diff --git a/.vscode/launch.json b/.vscode/launch.json index 6d4032ad3..7aa408a03 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -47,6 +47,21 @@ "localRoot": "${workspaceFolder}/apps/pwabuilder-google-play", "remoteRoot": "${workspaceFolder}/apps/pwabuilder-google-play" }, + { + "name": "Attach: Meta Horizon Service", + "type": "node", + "request": "attach", + "preLaunchTask": "tasks.meta-horizon:dev", + "env": { + "NODE_ENV": "test" + }, + "port": 5859, + "skipFiles": [ + "/**" + ], + "localRoot": "${workspaceFolder}/apps/pwabuilder-meta-horizon", + "remoteRoot": "${workspaceFolder}/apps/pwabuilder-meta-horizon" + }, { "name": "launch.microsoft-package-generator", "type": "coreclr", @@ -68,7 +83,8 @@ "configurations": [ "launch.backend", "launch.microsoft-package-generator", - "Attach: Google Play Service" + "Attach: Google Play Service", + "Attach: Meta Horizon Service" ] } ] diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 5926f0f10..8c8a3d0b0 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -152,6 +152,29 @@ "NODE_ENV": "test" } } + }, + { + "label": "tasks.meta-horizon:install", + "type": "npm", + "path": "apps/pwabuilder-meta-horizon", + "script": "install", + "isBackground": true, + "detail": "npm install for pwabuilder-meta-horizon" + }, + { + "label": "tasks.meta-horizon:dev", + "type": "npm", + "path": "apps/pwabuilder-meta-horizon", + "script": "dev", + "dependsOn": "tasks.meta-horizon:install", + "isBackground": true, + "detail": "npm run dev for pwabuilder-meta-horizon", + "problemMatcher": [], + "options": { + "env": { + "NODE_ENV": "test" + } + } } ], "inputs": [ diff --git a/apps/pwabuilder-meta-horizon/.dockerignore b/apps/pwabuilder-meta-horizon/.dockerignore new file mode 100644 index 000000000..b512c09d4 --- /dev/null +++ b/apps/pwabuilder-meta-horizon/.dockerignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/apps/pwabuilder-meta-horizon/.gitignore b/apps/pwabuilder-meta-horizon/.gitignore new file mode 100644 index 000000000..0827bc03c --- /dev/null +++ b/apps/pwabuilder-meta-horizon/.gitignore @@ -0,0 +1,147 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +#remove js files +**/*.js +**/*.js.map +!static/*.js + +# 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 (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# 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 + +# next.js build output +.next + +# Built application files +*.apk +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +.idea/caches +# Android Studio 3 in .gitignore file. +.idea/caches/build_file_checksums.ser +.idea/modules.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ + +# Visual Studio +/.vs diff --git a/apps/pwabuilder-meta-horizon/.vscode/launch.json b/apps/pwabuilder-meta-horizon/.vscode/launch.json new file mode 100644 index 000000000..df3955255 --- /dev/null +++ b/apps/pwabuilder-meta-horizon/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "program": "${workspaceFolder}/server.js", + "skipFiles": [ + "/**" + ] + } + + ] +} diff --git a/apps/pwabuilder-meta-horizon/Dockerfile b/apps/pwabuilder-meta-horizon/Dockerfile new file mode 100644 index 000000000..9e0c3d8fd --- /dev/null +++ b/apps/pwabuilder-meta-horizon/Dockerfile @@ -0,0 +1,106 @@ +# ─── PRELIMINAR: Ubuntu base con Android/Java toolchain ─────────────────────── +FROM --platform=linux/amd64 ubuntu:24.04 AS ubuntu + +ARG ANDROID_SDK_TOOLS_VERSION="11076708" +ARG NODE_VERSION="20.x" +ARG JENV_RELEASE="0.5.6" + +ARG DIRWORK="/tmp" +ARG SDK_PACKAGES_LIST="${DIRWORK}/packages.txt" + +ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 +ENV PATH=${JAVA_HOME}/bin:${PATH} + +ENV ANDROID_HOME="/opt/android-sdk" \ + ANDROID_SDK_HOME="/opt/android-sdk" \ + ANDROID_NDK="/opt/android-sdk/ndk/latest" \ + ANDROID_NDK_ROOT="/opt/android-sdk/ndk/latest" \ + FLUTTER_HOME="/opt/flutter" \ + JENV_ROOT="/opt/jenv" \ + ANDROID_SDK_MANAGER=/opt/android-sdk/cmdline-tools/latest/bin/sdkmanager \ + TZ=America/Los_Angeles \ + LANG="en_US.UTF-8" \ + LANGUAGE="en_US.UTF-8" \ + LC_ALL="en_US.UTF-8" + +ENV PATH="${JENV_ROOT}/shims:${JENV_ROOT}/bin:$JAVA_HOME/bin:$PATH:$ANDROID_SDK_HOME/emulator:$ANDROID_SDK_HOME/cmdline-tools/latest/bin:$ANDROID_SDK_HOME/tools:$ANDROID_SDK_HOME/platform-tools:$ANDROID_NDK:$FLUTTER_HOME/bin:$FLUTTER_HOME/bin/cache/dart-sdk/bin" + +FROM ubuntu AS pre-base +ARG TERM=dumb +ARG DEBIAN_FRONTEND=noninteractive +WORKDIR ${DIRWORK} + +RUN echo export JAVA_HOME="/usr/lib/jvm/java-17-openjdk-$(uname -m)/" >> /etc/jdk.env && \ + echo . /etc/jdk.env >> /etc/bash.bashrc && echo . /etc.jdk.env >> /etc/profile && \ + apt-get update -qq && \ + apt-get install -qq --no-install-recommends \ + apt-utils locales unzip curl git git-lfs build-essential openjdk-11-jdk openjdk-17-jdk \ + wget zip zipalign ruby-full python3-pip tzdata && \ + locale-gen $LANG && \ + git lfs install && \ + ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone && \ + rm -rf /var/lib/apt/lists/* + + +FROM pre-base AS base + +RUN wget --quiet -O sdk-tools.zip \ + "https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_TOOLS_VERSION}_latest.zip" && \ + mkdir -p "$ANDROID_HOME/cmdline-tools" && \ + unzip -q sdk-tools.zip -d "$ANDROID_HOME/cmdline-tools" && \ + rm sdk-tools.zip && \ + mv "$ANDROID_HOME/cmdline-tools/cmdline-tools" "$ANDROID_HOME/cmdline-tools/latest" && \ + echo yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --licenses && \ + echo yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "platform-tools" && \ + echo yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "platforms;android-31" && \ + echo yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "platforms;android-36" && \ + echo yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "build-tools;35.0.0" && \ + echo yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "build-tools;34.0.0" && \ + echo yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --licenses + + +FROM base AS jenv-base +RUN git clone --depth 1 --branch ${JENV_RELEASE} https://github.com/jenv/jenv.git ${JENV_ROOT} && \ + echo 'eval "$(jenv init -)"' >> ~/.bash_profile && \ + . ~/.bash_profile && . /etc/jdk.env && \ + jenv add /usr/lib/jvm/java-11-openjdk-$(uname -m) && \ + jenv add /usr/lib/jvm/java-17-openjdk-$(uname -m) && \ + jenv global 17.0 + + +FROM base AS pre-minimal +RUN mkdir -p "$ANDROID_HOME"/.android && \ + echo '### User Sources for Android SDK Manager' > "$ANDROID_HOME"/.android/repositories.cfg && \ + echo yes | ${ANDROID_SDK_MANAGER} --licenses > /dev/null && \ + ${ANDROID_SDK_MANAGER} --list > ${SDK_PACKAGES_LIST} + + +FROM pre-minimal AS node-final +RUN curl -sL https://deb.nodesource.com/setup_${NODE_VERSION} | bash - && \ + apt-get update -qq && apt-get install -qq nodejs yarn && \ + npm install -g npm@latest && \ + npm install -g bower cordova eslint gulp-cli @ionic/cli mocha node-gyp + +FROM node-final AS builder +WORKDIR /app + +COPY package*.json tsconfig.json ./ +COPY . . + +ENV NODE_ENV=development +RUN npm install +RUN npm run build +RUN ls -la /app/server.* + +FROM builder AS app +WORKDIR /app + +COPY --from=builder /app/package*.json ./ +RUN npm install --omit=dev --ignore-scripts + +COPY --from=builder /app . + +ENV PORT=5858 +EXPOSE 5858 + +CMD npm start diff --git a/apps/pwabuilder-meta-horizon/LICENSE.txt b/apps/pwabuilder-meta-horizon/LICENSE.txt new file mode 100644 index 000000000..bf390e5f8 --- /dev/null +++ b/apps/pwabuilder-meta-horizon/LICENSE.txt @@ -0,0 +1,13 @@ +PWABuilder Meta Horizon Packaging Service + +Copyright (c) Microsoft Corporation + +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/apps/pwabuilder-meta-horizon/README.md b/apps/pwabuilder-meta-horizon/README.md new file mode 100644 index 000000000..f5883b0a2 --- /dev/null +++ b/apps/pwabuilder-meta-horizon/README.md @@ -0,0 +1,42 @@ +# Meta Horizon Packaging Service + +This is PWABuilder's Meta Horizon platform that generates a Meta Horizon Store-ready app package (an `.aab` file) from a Progressive Web App using Android's Trusted Web Activity technology, configured for Meta Quest headsets. + +We utilize [Meta's fork of Bubblewrap](https://github.com/meta-quest/bubblewrap) (`@meta-quest/bubblewrap-core`) to generate and sign an Android app package targeting Horizon OS. See the [Meta Horizon PWA packaging documentation](https://developers.meta.com/horizon/documentation/web/pwa-packaging/) for full guidance on the Bubblewrap CLI options used by this service. + +This tool generates a zip file containing both an `.apk` file (for sideloading and testing on a Quest device) and an `.aab` file (for submission to the Meta Horizon Store). + +This app uses [PWABuilder's Android Build Box](https://github.com/pwa-builder/docker-android-build-box) docker image, which contains the necessary Android SDK Build Tools to execute Bubblewrap. + +## Issues + +Please use our [main repository for any issues/bugs/features suggestions](https://github.com/pwa-builder/PWABuilder/issues/new/choose). + +## Running Locally + +Steps: + +1. Configure environment files: in `apps/pwabuilder-meta-horizon/env/test.env`, set the paths for your JDK and AndroidTools. + +2. Launch the service: open the Run and Debug panel in VS Code, select "Launch Program", and press F5 to start the Meta Horizon package generator API. + +3. Visit `localhost` to see the testing interface. + +The response will be a zip file containing the generated app. + +Alternatively, you can build and run the service using Docker: + +```bash +npm run docker:build +npm run docker:run +``` + +This will start the docker container. Open a browser to `localhost:5859` to see the testing interface. + +## More info + +Once a Meta Horizon Store app package has been generated, follow the steps on the [Meta Horizon PWA packaging documentation](https://developers.meta.com/horizon/documentation/web/pwa-packaging/) to publish to the Meta Horizon Store. + +## Deploy + +Deploys are automatically pushed to the Meta Horizon staging slot. To deploy to production, swap staging and production. diff --git a/apps/pwabuilder-meta-horizon/app.ts b/apps/pwabuilder-meta-horizon/app.ts new file mode 100644 index 000000000..68c5f738f --- /dev/null +++ b/apps/pwabuilder-meta-horizon/app.ts @@ -0,0 +1,17 @@ +import express from 'express'; +import router from './routes/project.js'; +import cors from 'cors'; +import bodyParser from 'body-parser'; + +const app = express(); + +app.use(bodyParser.json()); +app.use( + cors({ + origin: '*', + }) +); +app.use('/', router); +app.use(express.static('static')); + +export default app; diff --git a/apps/pwabuilder-meta-horizon/azure-pipelines.yml b/apps/pwabuilder-meta-horizon/azure-pipelines.yml new file mode 100644 index 000000000..6938dd3fa --- /dev/null +++ b/apps/pwabuilder-meta-horizon/azure-pipelines.yml @@ -0,0 +1,39 @@ +# Docker +# Build and push an image to Azure Container Registry +# https://docs.microsoft.com/azure/devops/pipelines/languages/docker + +trigger: +- master + +resources: +- repo: self + +variables: + # Container registry service connection established during pipeline creation + dockerRegistryServiceConnection: '57b45fcd-5ca7-4036-b3ee-d80980e8ecc1' + imageRepository: 'pwabuildermetahorizon' + containerRegistry: 'pwabuilder.azurecr.io' + dockerfilePath: '$(Build.SourcesDirectory)/Dockerfile' + tag: '$(Build.BuildId)' + + # Agent VM image name + vmImageName: 'ubuntu-latest' + +stages: +- stage: Build + displayName: Build and push stage + jobs: + - job: Build + displayName: Build + pool: + vmImage: $(vmImageName) + steps: + - task: Docker@2 + displayName: Build and push an image to container registry + inputs: + command: buildAndPush + repository: $(imageRepository) + dockerfile: $(dockerfilePath) + containerRegistry: $(dockerRegistryServiceConnection) + tags: | + $(tag) diff --git a/apps/pwabuilder-meta-horizon/env/development.env b/apps/pwabuilder-meta-horizon/env/development.env new file mode 100644 index 000000000..e371267d3 --- /dev/null +++ b/apps/pwabuilder-meta-horizon/env/development.env @@ -0,0 +1,11 @@ +# Environment +NODE_ENV=development + +# Server +PORT=5859 +HOST=localhost + +JDK8PATH=/usr/lib/jvm/java-17-openjdk-amd64/ +ANDROIDTOOLSPATH=/opt/android-sdk + +ANALYSISLOGURL="" diff --git a/apps/pwabuilder-meta-horizon/env/production.env b/apps/pwabuilder-meta-horizon/env/production.env new file mode 100644 index 000000000..debf23baf --- /dev/null +++ b/apps/pwabuilder-meta-horizon/env/production.env @@ -0,0 +1,11 @@ +# Environment +NODE_ENV=production + +# Server +PORT=8081 +HOST=https://pwabuilder-meta-horizon.azurewebsites.net + +JDK8PATH=/usr/lib/jvm/java-17-openjdk-amd64/ +ANDROIDTOOLSPATH=/opt/android-sdk + +ANALYSISLOGURL="" diff --git a/apps/pwabuilder-meta-horizon/env/staging.env b/apps/pwabuilder-meta-horizon/env/staging.env new file mode 100644 index 000000000..8e61d9ffa --- /dev/null +++ b/apps/pwabuilder-meta-horizon/env/staging.env @@ -0,0 +1,11 @@ +# Environment +NODE_ENV=preview + +# Server +PORT=8081 +HOST=https://pwabuilder-meta-horizon-staging.azurewebsites.net/ + +JDK8PATH=/usr/lib/jvm/java-17-openjdk-amd64/ +ANDROIDTOOLSPATH=/opt/android-sdk + +ANALYSISLOGURL="" diff --git a/apps/pwabuilder-meta-horizon/env/test.env b/apps/pwabuilder-meta-horizon/env/test.env new file mode 100644 index 000000000..f61f3ea41 --- /dev/null +++ b/apps/pwabuilder-meta-horizon/env/test.env @@ -0,0 +1,11 @@ +# Environment +NODE_ENV=test + +# Server +PORT=80 +HOST=localhost + +JDK8PATH=/usr/lib/jvm/java-17-openjdk-amd64/ +ANDROIDTOOLSPATH=/opt/android-sdk + +ANALYSISLOGURL="" diff --git a/apps/pwabuilder-meta-horizon/models/appPackageRequest.ts b/apps/pwabuilder-meta-horizon/models/appPackageRequest.ts new file mode 100644 index 000000000..cd6c5a85a --- /dev/null +++ b/apps/pwabuilder-meta-horizon/models/appPackageRequest.ts @@ -0,0 +1,15 @@ +import { MetaHorizonPackageOptions } from "./metaHorizonPackageOptions.js"; + +/** + * The data stored inside a request to /generateAppPackage. + */ +export interface AppPackageRequest { + /** + * The MetaHorizonPackageOptions passed in the request. + */ + options: MetaHorizonPackageOptions | null; + /** + * Validation errors resulting from the request. If populated, this signals the request is malformed. + */ + validationErrors: string[]; +} diff --git a/apps/pwabuilder-meta-horizon/models/generatedAppPackage.ts b/apps/pwabuilder-meta-horizon/models/generatedAppPackage.ts new file mode 100644 index 000000000..93bc1227d --- /dev/null +++ b/apps/pwabuilder-meta-horizon/models/generatedAppPackage.ts @@ -0,0 +1,29 @@ +import { LocalKeyFileSigningOptions } from "./signingOptions.js"; + +/** + * The output of this tool: an APK file for testing locally on a device or Quest emulator. + * This will also contain the signing info, asset links files, and app bundle file + * when a signing key is supplied. + */ +export interface GeneratedAppPackage { + /** + * The path to the .aab app bundle file. This is the file that is submitted to the Meta Horizon Store. This will be null when no signing key is supplied. + */ + appBundleFilePath: string | null; + /** + * The path to the .apk file. This file is used for testing your app on a Meta Quest device or emulator. + */ + apkFilePath: string; + /** + * The signing info containing the signing key, store password, key password, alias, and additional signing information. This will be null if no signing key was supplied. + */ + signingInfo: LocalKeyFileSigningOptions | null; + /** + * The path to the digital asset links file (assetlinks.json). This file must be uploaded to https://my-awesome-pwa.com/.well-known/assetlinks.json to prove domain ownership. Otherwise, the app will load with the browser address bar visible. This will be null when no signing key is supplied. + */ + assetLinkFilePath: string | null; + /** + * The directory containing the source code of the project. + */ + projectDirectory: string; +} diff --git a/apps/pwabuilder-meta-horizon/models/metaHorizonPackageJob.ts b/apps/pwabuilder-meta-horizon/models/metaHorizonPackageJob.ts new file mode 100644 index 000000000..802b4c9a9 --- /dev/null +++ b/apps/pwabuilder-meta-horizon/models/metaHorizonPackageJob.ts @@ -0,0 +1,14 @@ +import { MetaHorizonPackageOptions } from "./metaHorizonPackageOptions.js"; + +export interface MetaHorizonPackageJob { + id: string; + pwaUrl: string; + analysisId: string | null; + status: "Queued" | "InProgress" | "Completed" | "Failed"; + createdAt: string; + retryCount: number; + errors: string[]; + logs: string[]; + packageOptions: MetaHorizonPackageOptions; + uploadedBlobFileName: string | null; +} diff --git a/apps/pwabuilder-meta-horizon/models/metaHorizonPackageOptions.ts b/apps/pwabuilder-meta-horizon/models/metaHorizonPackageOptions.ts new file mode 100644 index 000000000..ac078e8ee --- /dev/null +++ b/apps/pwabuilder-meta-horizon/models/metaHorizonPackageOptions.ts @@ -0,0 +1,211 @@ +import { AppsFlyerConfig } from "@meta-quest/bubblewrap-core/dist/lib/features/AppsFlyerFeature.js"; +import { ArCoreConfig } from "@meta-quest/bubblewrap-core/dist/lib/features/ArCoreFeature.js"; +import { FirstRunFlagConfig } from "@meta-quest/bubblewrap-core/dist/lib/features/FirstRunFlagFeature.js"; +import { HorizonBillingConfig } from "@meta-quest/bubblewrap-core/dist/lib/features/HorizonBillingFeature.js"; +import { HorizonPlatformSDKConfig } from "@meta-quest/bubblewrap-core/dist/lib/features/HorizonPlatformSDKFeature.js"; +import { LocationDelegationConfig } from "@meta-quest/bubblewrap-core/dist/lib/features/LocationDelegationFeature.js"; +import { PlayBillingConfig } from "@meta-quest/bubblewrap-core/dist/lib/features/PlayBillingFeature.js"; +import { ShareTarget, WebManifestShortcutJson } from "@meta-quest/bubblewrap-core/dist/lib/types/WebManifest.js"; +import { SigningOptions } from "./signingOptions.js"; + +/** + * Options for generating a Meta Horizon Store app package. + * + * The Meta Horizon platform builds a Trusted Web Activity APK / AAB targeting Meta Quest + * headsets, so most of the fields mirror the Google Play TWA options. Notable additions + * are Horizon-specific fields like `horizonOSAppMode`, `enableXRScene`, `enableMicrophone`, + * and Horizon-specific feature configs (`horizonBilling`, `horizonPlatformSDK`, `arCore`). + */ +export type MetaHorizonPackageOptions = { + /** + * The ID of the PWABuilder analysis that is associated with this Meta Horizon app package creation. May be null if the package creation was triggered outside of PWABuilder. + */ + analysisId: string | null; + /** + * Additional origins to be considered trusted by the app. + */ + additionalTrustedOrigins?: string[], + /** + * The app version. + */ + appVersion: string; + /** + * The app version code. + */ + appVersionCode: number; + /** + * The app background color. + */ + backgroundColor: string; + /** + * Display mode. + * standalone means the app displays like a normal app: with status bar and navbar visible. Recommended for most PWAs. + * fullscreen means the app takes up every bit of available real-estate, no status bar or nav bar is visible. Recommended for games and immersive media experiences. + * fullscreen-sticky is like fullscreen, except that when the user swipes from the device edge, the system bars appear semi-transparent, and the touch gesture is passed to your app so it can respond. Recommended for drawing apps and games that involve much swiping. + */ + display: "standalone" | "fullscreen" | "fullscreen-sticky"; + /** + * Whether to use Push Notification Delegation. If enabled, the TWA will be able to send push notifications without browser permission prompts. + */ + enableNotifications: boolean; + /** + * Whether to grant the app the Android microphone permission. Useful for PWAs that use the Web Speech / WebRTC APIs from inside the Quest browser. + */ + enableMicrophone?: boolean; + /** + * Whether to grant the app the Horizon OS USE_SCENE permission so the PWA can use the WebXR scene mesh / spatial anchors. + */ + enableXRScene?: boolean; + /** + * Whether to show "site settings" / "app settings" shortcut for installed apps. + */ + enableSiteSettingsShortcut?: boolean; + /** + * Fallback behavior. "customtabs" = use Chrome's Custom Tabs feature as a fallback. "webview" = use a embedded web view as a fallback behavior. + */ + fallbackType?: "customtabs" | "webview"; + /** + * Optional feature configurations. + */ + features?: { + appsFlyer?: AppsFlyerConfig; + locationDelegation?: LocationDelegationConfig; + playBilling?: PlayBillingConfig; + horizonBilling?: HorizonBillingConfig; + horizonPlatformSDK?: HorizonPlatformSDKConfig; + firstRunFlag?: FirstRunFlagConfig; + arCore?: ArCoreConfig; + }; + /** + * The URL host for the TWA, e.g. https://foo.com + */ + host: string; + /** + * Horizon OS app mode. Controls how the PWA is presented inside Horizon OS — `'2D'` runs the + * PWA as a flat panel app, `'immersive'` runs it as an immersive WebXR experience. + * Mirrors the bubblewrap `horizonOSAppMode` field. Defaults to `'immersive'`. + */ + horizonOSAppMode?: "2D" | "immersive"; + /** + * The Meta Horizon application ID (a numeric string from Meta Developer Dashboard, e.g. the + * number visible in the URL when viewing the app on the App Manager page: + * developers.meta.com/horizon/manage/applications/``/). + * + * In the generated Android project, this is emitted as the `com.meta.horizon.platform.ovr.OCULUS_APP_ID` + * AndroidManifest meta-data and is REQUIRED for in-app purchases / Meta Horizon Store APIs to + * work at runtime. Defaults to `'0'` in bubblewrap if not provided. Note: this is distinct from + * `packageId`, which is the Android applicationId (e.g. `com.mycompany.foo`). + */ + metaHorizonAppId?: string; + /** + * The URL to the icon to use for the app. + */ + iconUrl: string; + /** + * Whether to include source code in the Meta Horizon package. + */ + includeSourceCode: boolean; + /** + * The name of the app used on the launcher. This may be the same as name or may be a shortened version of that name to account for less available display space. + */ + launcherName: string; + /** + * The URL to the maskable icon to use for the app. + */ + maskableIconUrl?: string; + /** + * The URL to a monochrome icon to use for the app. + */ + monochromeIconUrl?: string; + /** + * The Android app name. + */ + name: string; + /** + * The color used for the navbar. + */ + navigationColor: string; + /** + * The color used for the navbar in dark mode. + */ + navigationColorDark?: string; + /** + * The color used for the navbar divider. + */ + navigationDividerColor?: string; + /** + * The color used for the navbar divider in dark mode. + */ + navigationDividerColorDark?: string; + /** + * Screen orientation hint. + */ + orientation?: "default" | "any" | "natural" | "landscape" | "portrait" | "portrait-primary" | "portrait-secondary" | "landscape-primary" | "landscape-secondary"; + /** + * The Android package ID to generate, e.g. com.mycompany.foo + */ + packageId: string; + /** + * The share target from the web manifest. + */ + shareTarget?: ShareTarget; + /* + * App shortcuts + */ + shortcuts?: WebManifestShortcutJson[]; + /** + * Details about the signing key. + * If .signingMode = "none", this will be ignored. + * If .signingMode = "new", this must contain the signing details, but .signing.file will be ignored. + * If .signingMode = "mine", this must contain the signing details and key file. + */ + signing?: SigningOptions | null; + /** + * The signing operation to perform. "new" means create a new signing key. "none" means don't sign. "mine" means use the uploaded signing information. + * If "new" in specified, then .signing must contain the details of the signing key. + * If "mine" is specified, then .signing must contain the .signing.file and details of the signing key. + */ + signingMode: "new" | "none" | "mine"; + /** + * How long the splash screen should fade out in milliseconds + */ + splashScreenFadeOutDuration: number; + /** + * The start url relative to the host. + */ + startUrl: string; + /** + * The app theme color, e.g. #2f3d58. + */ + themeColor: string; + /** + * The app theme color dark. + */ + themeColorDark?: string; + /** + * The URL to the web manifest. + */ + webManifestUrl: string; + /** + * The navigation scope that the browser considers to be within the app. If the user navigates outside the scope, it reverts to a normal web page inside a browser tab or window. Must be a full URL. Required for Meta Horizon devices. + */ + fullScopeUrl: string; + /** + * The minimum [Android API Level](https://developer.android.com/guide/topics/manifest/uses-sdk-element#ApiLevels) required for the application to run. Defaults to `23` for Meta Horizon (Quest minimum). + */ + minSdkVersion?: number; + /** + * The URL of the PWA as input to pwabuilder.com + */ + pwaUrl: string; + + /** + * Optional information to include with telemetry events fired during this package creation. + */ + analyticsInfo?: { + platformId?: string | null; + platformIdVersion?: string | null; + correlationId?: string | null; + referrer?: string | null; + } | null; +} diff --git a/apps/pwabuilder-meta-horizon/models/packageCreationProgress.ts b/apps/pwabuilder-meta-horizon/models/packageCreationProgress.ts new file mode 100644 index 000000000..8f7021d74 --- /dev/null +++ b/apps/pwabuilder-meta-horizon/models/packageCreationProgress.ts @@ -0,0 +1,4 @@ +export interface PackageCreationProgress { + message: string; + level?: "info" | "warn" | "error"; +} diff --git a/apps/pwabuilder-meta-horizon/models/packageJobLogger.ts b/apps/pwabuilder-meta-horizon/models/packageJobLogger.ts new file mode 100644 index 000000000..5e65053cb --- /dev/null +++ b/apps/pwabuilder-meta-horizon/models/packageJobLogger.ts @@ -0,0 +1,51 @@ +import { MetaHorizonPackageJob } from "./metaHorizonPackageJob.js"; +import { PackageCreationProgress } from "./packageCreationProgress.js"; + +/** + * A logger that writes logs to a Meta Horizon packaging job's log storage. + */ +export class PackageJobLogger { + /** + * Creates a new logger for the specified app package job. + */ + constructor(private readonly job: MetaHorizonPackageJob) { + } + + info(message: string, ...optionalArgs: any[]): void { + this.log("info", message, optionalArgs); + } + + warn(message: string, ...optionalArgs: any[]): void { + this.log("warn", message, optionalArgs); + } + + error(message: string, ...optionalArgs: any[]): void { + this.log("error", message, optionalArgs); + } + + logProgress(progress: PackageCreationProgress): void { + const level = progress.level || "info"; + this.log(level, progress.message); + } + + private log(level: "info" | "warn" | "error", message: string, ...optionalArgs: any[]): void { + const timestamp = new Date().toISOString(); + const optionsArgsStr = optionalArgs && optionalArgs.length > 0 ? " " + optionalArgs.map(arg => JSON.stringify(arg || "")).join(" ") : ""; + const logEntry = `${timestamp} [${level}]: ${message}${optionsArgsStr}`; + + this.job.logs.push(logEntry); + if (level === "error") { + this.job.errors.push(logEntry); + } + + const consoleMethod = level === "warn" ? console.warn + : level === "error" ? console.error + : console.info; + + if (optionalArgs && optionalArgs.length > 0) { + consoleMethod(message, ...optionalArgs); + } else { + consoleMethod(message); + } + } +} diff --git a/apps/pwabuilder-meta-horizon/models/signingOptions.ts b/apps/pwabuilder-meta-horizon/models/signingOptions.ts new file mode 100644 index 000000000..94faea00a --- /dev/null +++ b/apps/pwabuilder-meta-horizon/models/signingOptions.ts @@ -0,0 +1,17 @@ +export interface SigningOptions { + /** + * The data of the Android keystore signing file encoded as a base64 string. + */ + file: string | null; + alias: string; + fullName?: string | null; + organization?: string | null; + organizationalUnit?: string | null; + countryCode: string | null; + keyPassword: string; + storePassword: string; +} + +export interface LocalKeyFileSigningOptions extends SigningOptions { + keyFilePath: string; +} diff --git a/apps/pwabuilder-meta-horizon/nodemon.json b/apps/pwabuilder-meta-horizon/nodemon.json new file mode 100644 index 000000000..71e24778a --- /dev/null +++ b/apps/pwabuilder-meta-horizon/nodemon.json @@ -0,0 +1,6 @@ +{ + "watch": ["./"], + "ext": "ts,js,json", + "ignore": ["node_modules"], + "exec": "node --inspect=5859 --loader ts-node/esm ./server.ts" +} diff --git a/apps/pwabuilder-meta-horizon/package-lock.json b/apps/pwabuilder-meta-horizon/package-lock.json new file mode 100644 index 000000000..8fca531b8 --- /dev/null +++ b/apps/pwabuilder-meta-horizon/package-lock.json @@ -0,0 +1,7741 @@ +{ + "name": "@pwabuilder/cloudmetahorizon", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@pwabuilder/cloudmetahorizon", + "version": "1.0.0", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "@azure/identity": "^4.13.0", + "@azure/monitor-opentelemetry": "^1.7.0", + "@azure/storage-blob": "^12.29.0", + "@meta-quest/bubblewrap-core": "^1.24.1", + "@opentelemetry/api": "^1.9.0", + "@redis/client": "^5.10.0", + "@redis/entraid": "^5.10.0", + "@types/archiver": "^3.0.0", + "@types/cors": "^2.8.13", + "@types/escape-html": "^1.0.4", + "@types/express": "^4.17.2", + "@types/fs-extra": "^11.0.4", + "@types/tmp": "^0.2.6", + "archiver": "^5.3.2", + "body-parser": "^1.20.4", + "cors": "^2.8.5", + "del": "^7.1.0", + "dotenv": "^16.6.1", + "escape-html": "^1.0.3", + "express": "^4.22.1", + "fs-extra": "^11.3.3", + "node-fetch": "^3.3.2", + "password-generator": "^2.3.2", + "tmp": "^0.2.5", + "ts-node": "^10.9.2", + "typescript": "^5.9.3" + }, + "devDependencies": { + "cross-env": "^10.1.0" + } + }, + "node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.10.1.tgz", + "integrity": "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-util": "^1.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-client": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz", + "integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-rest-pipeline": "^1.22.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-http-compat": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-2.4.0.tgz", + "integrity": "sha512-f1P96IB399YiN2ARYHP7EpZi3Bf3wH4SN2lGzrw7JVwm7bbsVYtf2iKSBwTywD2P62NOPZGHFSZi+6jjb75JuA==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@azure/core-client": "^1.10.0", + "@azure/core-rest-pipeline": "^1.22.0" + } + }, + "node_modules/@azure/core-lro": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.7.2.tgz", + "integrity": "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-util": "^1.2.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-paging": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.6.2.tgz", + "integrity": "sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.23.0.tgz", + "integrity": "sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "@typespec/ts-http-runtime": "^0.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.3.1.tgz", + "integrity": "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.13.1.tgz", + "integrity": "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-xml": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@azure/core-xml/-/core-xml-1.5.1.tgz", + "integrity": "sha512-xcNRHqCoSp4AunOALEae6A8f3qATb83gSrm31Iqb01OzblvC3/W/bfXozcq78EzIdzZzuH1bZ2NvRR0TdX709w==", + "license": "MIT", + "dependencies": { + "fast-xml-parser": "^5.5.9", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/identity": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.1.tgz", + "integrity": "sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.9.0", + "@azure/core-client": "^1.9.2", + "@azure/core-rest-pipeline": "^1.17.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.11.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^5.5.0", + "@azure/msal-node": "^5.1.0", + "open": "^10.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/logger": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.3.0.tgz", + "integrity": "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==", + "license": "MIT", + "dependencies": { + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/monitor-opentelemetry": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@azure/monitor-opentelemetry/-/monitor-opentelemetry-1.16.0.tgz", + "integrity": "sha512-GUE4kowIKrqxzPjiy/o417jot4n0BILomMbDCeK9lOnLxV0VliccEuz+FJ9BgLWKWswr6H0Q2Nzpcx9GFuLHXw==", + "license": "MIT", + "dependencies": { + "@azure/core-auth": "^1.10.1", + "@azure/core-client": "^1.10.1", + "@azure/core-rest-pipeline": "^1.22.2", + "@azure/logger": "^1.3.0", + "@azure/monitor-opentelemetry-exporter": "1.0.0-beta.39", + "@azure/opentelemetry-instrumentation-azure-sdk": "^1.0.0-beta.9", + "@microsoft/applicationinsights-web-snippet": "^1.2.3", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api-logs": "^0.208.0", + "@opentelemetry/core": "^2.2.0", + "@opentelemetry/instrumentation": "^0.208.0", + "@opentelemetry/instrumentation-bunyan": "^0.54.0", + "@opentelemetry/instrumentation-http": "^0.208.0", + "@opentelemetry/instrumentation-mongodb": "^0.61.0", + "@opentelemetry/instrumentation-mysql": "^0.54.0", + "@opentelemetry/instrumentation-pg": "^0.61.0", + "@opentelemetry/instrumentation-redis": "^0.57.0", + "@opentelemetry/instrumentation-winston": "^0.53.0", + "@opentelemetry/resource-detector-azure": "^0.7.0", + "@opentelemetry/resources": "^2.2.0", + "@opentelemetry/sdk-logs": "^0.208.0", + "@opentelemetry/sdk-metrics": "^2.2.0", + "@opentelemetry/sdk-node": "^0.208.0", + "@opentelemetry/sdk-trace-base": "^2.2.0", + "@opentelemetry/sdk-trace-node": "^2.2.0", + "@opentelemetry/semantic-conventions": "^1.38.0", + "@opentelemetry/winston-transport": "^0.19.0", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/monitor-opentelemetry-exporter": { + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@azure/monitor-opentelemetry-exporter/-/monitor-opentelemetry-exporter-1.0.0-beta.39.tgz", + "integrity": "sha512-ljvm3PBe5em/kudINq3kA7wzwCQihYn/1HiO1/lKocK68QGahr6qiTwsadBXWJ2cDUS64iWbHtSM1TsJteBS0A==", + "license": "MIT", + "dependencies": { + "@azure/core-auth": "^1.9.0", + "@azure/core-client": "^1.9.2", + "@azure/core-rest-pipeline": "^1.19.0", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api-logs": "^0.205.0", + "@opentelemetry/core": "^2.1.0", + "@opentelemetry/resources": "^2.1.0", + "@opentelemetry/sdk-logs": "^0.205.0", + "@opentelemetry/sdk-metrics": "^2.1.0", + "@opentelemetry/sdk-trace-base": "^2.1.0", + "@opentelemetry/semantic-conventions": "^1.37.0", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/monitor-opentelemetry-exporter/node_modules/@opentelemetry/api-logs": { + "version": "0.205.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.205.0.tgz", + "integrity": "sha512-wBlPk1nFB37Hsm+3Qy73yQSobVn28F4isnWIBvKpd5IUH/eat8bwcL02H9yzmHyyPmukeccSl2mbN5sDQZYnPg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@azure/monitor-opentelemetry-exporter/node_modules/@opentelemetry/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", + "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@azure/monitor-opentelemetry-exporter/node_modules/@opentelemetry/resources": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.1.0.tgz", + "integrity": "sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@azure/monitor-opentelemetry-exporter/node_modules/@opentelemetry/sdk-logs": { + "version": "0.205.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.205.0.tgz", + "integrity": "sha512-nyqhNQ6eEzPWQU60Nc7+A5LIq8fz3UeIzdEVBQYefB4+msJZ2vuVtRuk9KxPMw1uHoHDtYEwkr2Ct0iG29jU8w==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.205.0", + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@azure/msal-browser": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.9.0.tgz", + "integrity": "sha512-CzE+4PefDSJWj26zU7G1bKchlGRRHMBFreG4tAlGuzyI8hAPiYGobaJvZBgZBf6L63iphX7VH+ityL8VgEQz9Q==", + "license": "MIT", + "dependencies": { + "@azure/msal-common": "16.5.2" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "16.5.2", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.5.2.tgz", + "integrity": "sha512-GkDEL6TYo3HgT3UuqakdgE9PZfc1hMki6+Hwgy1uddb/EauvAKfu85vVhuofRSo22D1xTnWt8Ucwfg4vSCVwvA==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.1.5.tgz", + "integrity": "sha512-ObTeMoNPmq19X3z40et9Xvs4ZoWVeJg43PZMRLG5iwVL+2nCtAerG3YTDItqPp1CfXNwmCXBbg8jn1DOx65c3g==", + "license": "MIT", + "dependencies": { + "@azure/msal-common": "16.5.2", + "jsonwebtoken": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@azure/opentelemetry-instrumentation-azure-sdk": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@azure/opentelemetry-instrumentation-azure-sdk/-/opentelemetry-instrumentation-azure-sdk-1.0.0.tgz", + "integrity": "sha512-Y8rZOIMXQY/GwNRL+uLVuwIn9aEa/KnnggyYUmFxC1MigmRJCNH5NxMmxKSpddXF9SW6Z1ijRd6Pptd2A5OhGw==", + "license": "MIT", + "dependencies": { + "@azure/core-tracing": "^1.2.0", + "@azure/logger": "^1.0.0", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/sdk-trace-web": "^2.0.0", + "tslib": "^2.7.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/opentelemetry-instrumentation-azure-sdk/node_modules/@opentelemetry/api-logs": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.211.0.tgz", + "integrity": "sha512-swFdZq8MCdmdR22jTVGQDhwqDzcI4M10nhjXkLr1EsIzXgZBqm4ZlmmcWsg3TSNf+3mzgOiqveXmBLZuDi2Lgg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@azure/opentelemetry-instrumentation-azure-sdk/node_modules/@opentelemetry/instrumentation": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.211.0.tgz", + "integrity": "sha512-h0nrZEC/zvI994nhg7EgQ8URIHt0uDTwN90r3qQUdZORS455bbx+YebnGeEuFghUT0HlJSrLF4iHw67f+odY+Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.211.0", + "import-in-the-middle": "^2.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@azure/storage-blob": { + "version": "12.31.0", + "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.31.0.tgz", + "integrity": "sha512-DBgNv10aCSxopt92DkTDD0o9xScXeBqPKGmR50FPZQaEcH4JLQ+GEOGEDv19V5BMkB7kxr+m4h6il/cCDPvmHg==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.9.0", + "@azure/core-client": "^1.9.3", + "@azure/core-http-compat": "^2.2.0", + "@azure/core-lro": "^2.2.0", + "@azure/core-paging": "^1.6.2", + "@azure/core-rest-pipeline": "^1.19.1", + "@azure/core-tracing": "^1.2.0", + "@azure/core-util": "^1.11.0", + "@azure/core-xml": "^1.4.5", + "@azure/logger": "^1.1.4", + "@azure/storage-common": "^12.3.0", + "events": "^3.0.0", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/storage-common": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@azure/storage-common/-/storage-common-12.3.0.tgz", + "integrity": "sha512-/OFHhy86aG5Pe8dP5tsp+BuJ25JOAl9yaMU3WZbkeoiFMHFtJ7tu5ili7qEdBXNW9G5lDB19trwyI6V49F/8iQ==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.9.0", + "@azure/core-http-compat": "^2.2.0", + "@azure/core-rest-pipeline": "^1.19.1", + "@azure/core-tracing": "^1.2.0", + "@azure/core-util": "^1.11.0", + "@azure/logger": "^1.1.4", + "events": "^3.3.0", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@epic-web/invariant": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", + "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", + "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.3", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jimp/bmp": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.12.tgz", + "integrity": "sha512-aeI64HD0npropd+AR76MCcvvRaa+Qck6loCOS03CkkxGHN5/r336qTM5HPUdHKMDOGzqknuVPA8+kK1t03z12g==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12", + "bmp-js": "^0.1.0" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/core": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.22.12.tgz", + "integrity": "sha512-l0RR0dOPyzMKfjUW1uebzueFEDtCOj9fN6pyTYWWOM/VS4BciXQ1VVrJs8pO3kycGYZxncRKhCoygbNr8eEZQA==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12", + "any-base": "^1.1.0", + "buffer": "^5.2.0", + "exif-parser": "^0.1.12", + "file-type": "^16.5.4", + "isomorphic-fetch": "^3.0.0", + "pixelmatch": "^4.0.2", + "tinycolor2": "^1.6.0" + } + }, + "node_modules/@jimp/custom": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.12.tgz", + "integrity": "sha512-xcmww1O/JFP2MrlGUMd3Q78S3Qu6W3mYTXYuIqFq33EorgYHV/HqymHfXy9GjiCJ7OI+7lWx6nYFOzU7M4rd1Q==", + "license": "MIT", + "dependencies": { + "@jimp/core": "^0.22.12" + } + }, + "node_modules/@jimp/gif": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.22.12.tgz", + "integrity": "sha512-y6BFTJgch9mbor2H234VSjd9iwAhaNf/t3US5qpYIs0TSbAvM02Fbc28IaDETj9+4YB4676sz4RcN/zwhfu1pg==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12", + "gifwrap": "^0.10.1", + "omggif": "^1.0.9" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/jpeg": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.22.12.tgz", + "integrity": "sha512-Rq26XC/uQWaQKyb/5lksCTCxXhtY01NJeBN+dQv5yNYedN0i7iYu+fXEoRsfaJ8xZzjoANH8sns7rVP4GE7d/Q==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12", + "jpeg-js": "^0.4.4" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-blit": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.12.tgz", + "integrity": "sha512-xslz2ZoFZOPLY8EZ4dC29m168BtDx95D6K80TzgUi8gqT7LY6CsajWO0FAxDwHz6h0eomHMfyGX0stspBrTKnQ==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-blur": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.22.12.tgz", + "integrity": "sha512-S0vJADTuh1Q9F+cXAwFPlrKWzDj2F9t/9JAbUvaaDuivpyWuImEKXVz5PUZw2NbpuSHjwssbTpOZ8F13iJX4uw==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-circle": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.22.12.tgz", + "integrity": "sha512-SWVXx1yiuj5jZtMijqUfvVOJBwOifFn0918ou4ftoHgegc5aHWW5dZbYPjvC9fLpvz7oSlptNl2Sxr1zwofjTg==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-color": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.12.tgz", + "integrity": "sha512-xImhTE5BpS8xa+mAN6j4sMRWaUgUDLoaGHhJhpC+r7SKKErYDR0WQV4yCE4gP+N0gozD0F3Ka1LUSaMXrn7ZIA==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12", + "tinycolor2": "^1.6.0" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-contain": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.22.12.tgz", + "integrity": "sha512-Eo3DmfixJw3N79lWk8q/0SDYbqmKt1xSTJ69yy8XLYQj9svoBbyRpSnHR+n9hOw5pKXytHwUW6nU4u1wegHNoQ==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blit": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5", + "@jimp/plugin-scale": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-cover": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.22.12.tgz", + "integrity": "sha512-z0w/1xH/v/knZkpTNx+E8a7fnasQ2wHG5ze6y5oL2dhH1UufNua8gLQXlv8/W56+4nJ1brhSd233HBJCo01BXA==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-crop": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5", + "@jimp/plugin-scale": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-crop": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.12.tgz", + "integrity": "sha512-FNuUN0OVzRCozx8XSgP9MyLGMxNHHJMFt+LJuFjn1mu3k0VQxrzqbN06yIl46TVejhyAhcq5gLzqmSCHvlcBVw==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-displace": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.22.12.tgz", + "integrity": "sha512-qpRM8JRicxfK6aPPqKZA6+GzBwUIitiHaZw0QrJ64Ygd3+AsTc7BXr+37k2x7QcyCvmKXY4haUrSIsBug4S3CA==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-dither": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.22.12.tgz", + "integrity": "sha512-jYgGdSdSKl1UUEanX8A85v4+QUm+PE8vHFwlamaKk89s+PXQe7eVE3eNeSZX4inCq63EHL7cX580dMqkoC3ZLw==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-fisheye": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.22.12.tgz", + "integrity": "sha512-LGuUTsFg+fOp6KBKrmLkX4LfyCy8IIsROwoUvsUPKzutSqMJnsm3JGDW2eOmWIS/jJpPaeaishjlxvczjgII+Q==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-flip": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.22.12.tgz", + "integrity": "sha512-m251Rop7GN8W0Yo/rF9LWk6kNclngyjIJs/VXHToGQ6EGveOSTSQaX2Isi9f9lCDLxt+inBIb7nlaLLxnvHX8Q==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-rotate": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-gaussian": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.22.12.tgz", + "integrity": "sha512-sBfbzoOmJ6FczfG2PquiK84NtVGeScw97JsCC3rpQv1PHVWyW+uqWFF53+n3c8Y0P2HWlUjflEla2h/vWShvhg==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-invert": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.22.12.tgz", + "integrity": "sha512-N+6rwxdB+7OCR6PYijaA/iizXXodpxOGvT/smd/lxeXsZ/empHmFFFJ/FaXcYh19Tm04dGDaXcNF/dN5nm6+xQ==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-mask": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.22.12.tgz", + "integrity": "sha512-4AWZg+DomtpUA099jRV8IEZUfn1wLv6+nem4NRJC7L/82vxzLCgXKTxvNvBcNmJjT9yS1LAAmiJGdWKXG63/NA==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-normalize": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.22.12.tgz", + "integrity": "sha512-0So0rexQivnWgnhacX4cfkM2223YdExnJTTy6d06WbkfZk5alHUx8MM3yEzwoCN0ErO7oyqEWRnEkGC+As1FtA==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-print": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.22.12.tgz", + "integrity": "sha512-c7TnhHlxm87DJeSnwr/XOLjJU/whoiKYY7r21SbuJ5nuH+7a78EW1teOaj5gEr2wYEd7QtkFqGlmyGXY/YclyQ==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12", + "load-bmfont": "^1.4.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blit": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-resize": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.12.tgz", + "integrity": "sha512-3NyTPlPbTnGKDIbaBgQ3HbE6wXbAlFfxHVERmrbqAi8R3r6fQPxpCauA8UVDnieg5eo04D0T8nnnNIX//i/sXg==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-rotate": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.12.tgz", + "integrity": "sha512-9YNEt7BPAFfTls2FGfKBVgwwLUuKqy+E8bDGGEsOqHtbuhbshVGxN2WMZaD4gh5IDWvR+emmmPPWGgaYNYt1gA==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blit": ">=0.3.5", + "@jimp/plugin-crop": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-scale": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.12.tgz", + "integrity": "sha512-dghs92qM6MhHj0HrV2qAwKPMklQtjNpoYgAB94ysYpsXslhRTiPisueSIELRwZGEr0J0VUxpUY7HgJwlSIgGZw==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-shadow": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.22.12.tgz", + "integrity": "sha512-FX8mTJuCt7/3zXVoeD/qHlm4YH2bVqBuWQHXSuBK054e7wFRnRnbSLPUqAwSeYP3lWqpuQzJtgiiBxV3+WWwTg==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blur": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-threshold": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.22.12.tgz", + "integrity": "sha512-4x5GrQr1a/9L0paBC/MZZJjjgjxLYrqSmWd+e+QfAEPvmRxdRoQ5uKEuNgXnm9/weHQBTnQBQsOY2iFja+XGAw==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-color": ">=0.8.0", + "@jimp/plugin-resize": ">=0.8.0" + } + }, + "node_modules/@jimp/plugins": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.22.12.tgz", + "integrity": "sha512-yBJ8vQrDkBbTgQZLty9k4+KtUQdRjsIDJSPjuI21YdVeqZxYywifHl4/XWILoTZsjTUASQcGoH0TuC0N7xm3ww==", + "license": "MIT", + "dependencies": { + "@jimp/plugin-blit": "^0.22.12", + "@jimp/plugin-blur": "^0.22.12", + "@jimp/plugin-circle": "^0.22.12", + "@jimp/plugin-color": "^0.22.12", + "@jimp/plugin-contain": "^0.22.12", + "@jimp/plugin-cover": "^0.22.12", + "@jimp/plugin-crop": "^0.22.12", + "@jimp/plugin-displace": "^0.22.12", + "@jimp/plugin-dither": "^0.22.12", + "@jimp/plugin-fisheye": "^0.22.12", + "@jimp/plugin-flip": "^0.22.12", + "@jimp/plugin-gaussian": "^0.22.12", + "@jimp/plugin-invert": "^0.22.12", + "@jimp/plugin-mask": "^0.22.12", + "@jimp/plugin-normalize": "^0.22.12", + "@jimp/plugin-print": "^0.22.12", + "@jimp/plugin-resize": "^0.22.12", + "@jimp/plugin-rotate": "^0.22.12", + "@jimp/plugin-scale": "^0.22.12", + "@jimp/plugin-shadow": "^0.22.12", + "@jimp/plugin-threshold": "^0.22.12", + "timm": "^1.6.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/png": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.22.12.tgz", + "integrity": "sha512-Mrp6dr3UTn+aLK8ty/dSKELz+Otdz1v4aAXzV5q53UDD2rbB5joKVJ/ChY310B+eRzNxIovbUF1KVrUsYdE8Hg==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12", + "pngjs": "^6.0.0" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/tiff": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.22.12.tgz", + "integrity": "sha512-E1LtMh4RyJsoCAfAkBRVSYyZDTtLq9p9LUiiYP0vPtXyxX4BiYBUYihTLSBlCQg5nF2e4OpQg7SPrLdJ66u7jg==", + "license": "MIT", + "dependencies": { + "utif2": "^4.0.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/types": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.22.12.tgz", + "integrity": "sha512-wwKYzRdElE1MBXFREvCto5s699izFHNVvALUv79GXNbsOVqlwlOxlWJ8DuyOGIXoLP4JW/m30YyuTtfUJgMRMA==", + "license": "MIT", + "dependencies": { + "@jimp/bmp": "^0.22.12", + "@jimp/gif": "^0.22.12", + "@jimp/jpeg": "^0.22.12", + "@jimp/png": "^0.22.12", + "@jimp/tiff": "^0.22.12", + "timm": "^1.6.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.13.3" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "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", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@maxim_mazurok/gapi.client.androidpublisher-v3": { + "version": "0.3.20260316", + "resolved": "https://registry.npmjs.org/@maxim_mazurok/gapi.client.androidpublisher-v3/-/gapi.client.androidpublisher-v3-0.3.20260316.tgz", + "integrity": "sha512-nBwcwZ4306W3E1kuB6gMKX1X2v+9OSOgcaBsruLm5M4Qp2SG+R0+Yq71tu8oJyLrk+XR2kULbQY7vBPvBKNS3g==", + "license": "MIT", + "dependencies": { + "@types/gapi.client": "*", + "@types/gapi.client.discovery-v1": "*" + } + }, + "node_modules/@maxim_mazurok/gapi.client.discovery-v1": { + "version": "0.5.20200806", + "resolved": "https://registry.npmjs.org/@maxim_mazurok/gapi.client.discovery-v1/-/gapi.client.discovery-v1-0.5.20200806.tgz", + "integrity": "sha512-oVq9hnnI5VhAtsx55iJbPz8NRfJtWFpI1kINKeuygzCvsx90b1GQDeN3MDUvhADXiQ7+Izs316cqBnJjoDxCow==", + "license": "MIT", + "dependencies": { + "@types/gapi.client": "*", + "@types/gapi.client.discovery-v1": "*" + } + }, + "node_modules/@meta-quest/bubblewrap-core": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/@meta-quest/bubblewrap-core/-/bubblewrap-core-1.24.1.tgz", + "integrity": "sha512-3E3SRhw9Gq9DyOFcRcCrQ5dWHirIjihhTrYPf/ftzv39NzDX0sVH0gtJYwMANNfGrrIcLBkk0bk1DEH4WUKqEA==", + "license": "Apache-2.0", + "dependencies": { + "@resvg/resvg-js": "^2.2.0", + "@types/color": "^3.0.0", + "@types/extract-zip": "^1.6.2", + "@types/gapi.client.androidpublisher": "^3.0.0", + "@types/inquirer": "^9.0.7", + "@types/lodash": "^4.17.14", + "@types/mime-types": "^2.1.0", + "@types/node": "^18.11.0", + "@types/node-fetch": "^2.5.10", + "@types/valid-url": "^1.0.3", + "canvg": "4.0.3", + "color": "^3.1.3", + "extract-zip": "^1.7.0", + "fetch-h2": "^2.5.1", + "googleapis": "^81.0.0", + "inquirer": "^9.0.7", + "jimp": "^0.22.7", + "lodash": "^4.17.21", + "mime-types": "^2.1.28", + "node-fetch": "^2.6.1", + "tar": "^7.5.3", + "valid-url": "^1.0.9" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@meta-quest/bubblewrap-core/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@meta-quest/bubblewrap-core/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/@meta-quest/bubblewrap-core/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/@microsoft/applicationinsights-web-snippet": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-snippet/-/applicationinsights-web-snippet-1.2.3.tgz", + "integrity": "sha512-59ex4x1/PabGQIg+o0GKG5olqAJYBvMOiXec/9HCD3hK2y36YMWT0ivq5mequvtS5+21kco3SOnMB6QyScLPIA==", + "license": "MIT" + }, + "node_modules/@nodable/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.208.0.tgz", + "integrity": "sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.7.1.tgz", + "integrity": "sha512-OPFBYuXEn1E4ja3Y6eeA7O+ZnLBNcXTV5Cgsn1VaqBZ6hC5FnpZPLBNme1LJY8ZtF4aOujPKFoeWN4ik487KuQ==", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-grpc": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.208.0.tgz", + "integrity": "sha512-AmZDKFzbq/idME/yq68M155CJW1y056MNBekH9OZewiZKaqgwYN4VYfn3mXVPftYsfrCM2r4V6tS8H2LmfiDCg==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/otlp-exporter-base": "0.208.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.208.0", + "@opentelemetry/otlp-transformer": "0.208.0", + "@opentelemetry/sdk-logs": "0.208.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.208.0.tgz", + "integrity": "sha512-jOv40Bs9jy9bZVLo/i8FwUiuCvbjWDI+ZW13wimJm4LjnlwJxGgB+N/VWOZUTpM+ah/awXeQqKdNlpLf2EjvYg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.208.0", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/otlp-exporter-base": "0.208.0", + "@opentelemetry/otlp-transformer": "0.208.0", + "@opentelemetry/sdk-logs": "0.208.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.208.0.tgz", + "integrity": "sha512-Wy8dZm16AOfM7yddEzSFzutHZDZ6HspKUODSUJVjyhnZFMBojWDjSNgduyCMlw6qaxJYz0dlb0OEcb4Eme+BfQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.208.0", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/otlp-exporter-base": "0.208.0", + "@opentelemetry/otlp-transformer": "0.208.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/sdk-logs": "0.208.0", + "@opentelemetry/sdk-trace-base": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz", + "integrity": "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.208.0.tgz", + "integrity": "sha512-YbEnk7jjYmvhIwp2xJGkEvdgnayrA2QSr28R1LR1klDPvCxsoQPxE6TokDbQpoCEhD3+KmJVEXfb4EeEQxjymg==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.208.0", + "@opentelemetry/otlp-exporter-base": "0.208.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.208.0", + "@opentelemetry/otlp-transformer": "0.208.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/sdk-metrics": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.2.0.tgz", + "integrity": "sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.208.0.tgz", + "integrity": "sha512-QZ3TrI90Y0i1ezWQdvreryjY0a5TK4J9gyDLIyhLBwV+EQUvyp5wR7TFPKCAexD4TDSWM0t3ulQDbYYjVtzTyA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/otlp-exporter-base": "0.208.0", + "@opentelemetry/otlp-transformer": "0.208.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/sdk-metrics": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.2.0.tgz", + "integrity": "sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.208.0.tgz", + "integrity": "sha512-CvvVD5kRDmRB/uSMalvEF6kiamY02pB46YAqclHtfjJccNZFxbkkXkMMmcJ7NgBFa5THmQBNVQ2AHyX29nRxOw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.208.0", + "@opentelemetry/otlp-exporter-base": "0.208.0", + "@opentelemetry/otlp-transformer": "0.208.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/sdk-metrics": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.2.0.tgz", + "integrity": "sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-prometheus": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.208.0.tgz", + "integrity": "sha512-Rgws8GfIfq2iNWCD3G1dTD9xwYsCof1+tc5S5X0Ahdb5CrAPE+k5P70XCWHqrFFurVCcKaHLJ/6DjIBHWVfLiw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/sdk-metrics": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.2.0.tgz", + "integrity": "sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.208.0.tgz", + "integrity": "sha512-E/eNdcqVUTAT7BC+e8VOw/krqb+5rjzYkztMZ/o+eyJl+iEY6PfczPXpwWuICwvsm0SIhBoh9hmYED5Vh5RwIw==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/otlp-exporter-base": "0.208.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.208.0", + "@opentelemetry/otlp-transformer": "0.208.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/sdk-trace-base": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz", + "integrity": "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.208.0.tgz", + "integrity": "sha512-jbzDw1q+BkwKFq9yxhjAJ9rjKldbt5AgIy1gmEIJjEV/WRxQ3B6HcLVkwbjJ3RcMif86BDNKR846KJ0tY0aOJA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/otlp-exporter-base": "0.208.0", + "@opentelemetry/otlp-transformer": "0.208.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/sdk-trace-base": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz", + "integrity": "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.208.0.tgz", + "integrity": "sha512-q844Jc3ApkZVdWYd5OAl+an3n1XXf3RWHa3Zgmnhw3HpsM3VluEKHckUUEqHPzbwDUx2lhPRVkqK7LsJ/CbDzA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/otlp-exporter-base": "0.208.0", + "@opentelemetry/otlp-transformer": "0.208.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/sdk-trace-base": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz", + "integrity": "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.2.0.tgz", + "integrity": "sha512-VV4QzhGCT7cWrGasBWxelBjqbNBbyHicWWS/66KoZoe9BzYwFB72SH2/kkc4uAviQlO8iwv2okIJy+/jqqEHTg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/sdk-trace-base": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz", + "integrity": "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.208.0.tgz", + "integrity": "sha512-Eju0L4qWcQS+oXxi6pgh7zvE2byogAkcsVv0OjHF/97iOz1N/aKE6etSGowYkie+YA1uo6DNwdSxaaNnLvcRlA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.208.0", + "import-in-the-middle": "^2.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-bunyan": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-bunyan/-/instrumentation-bunyan-0.54.0.tgz", + "integrity": "sha512-DnPoHSLcKwQmueW+7OOaXFD/cj1M6hqwTm6P88QdMbln/dqEatLxzt/ACPk4Yb5x4aU3ZLyeLyKxtzfhp76+aw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "^0.208.0", + "@opentelemetry/instrumentation": "^0.208.0", + "@types/bunyan": "1.8.11" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-http": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.208.0.tgz", + "integrity": "sha512-rhmK46DRWEbQQB77RxmVXGyjs6783crXCnFjYQj+4tDH/Kpv9Rbg3h2kaNyp5Vz2emF1f9HOQQvZoHzwMWOFZQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/instrumentation": "0.208.0", + "@opentelemetry/semantic-conventions": "^1.29.0", + "forwarded-parse": "2.1.2" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mongodb": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.61.0.tgz", + "integrity": "sha512-OV3i2DSoY5M/pmLk+68xr5RvkHU8DRB3DKMzYJdwDdcxeLs62tLbkmRyqJZsYf3Ht7j11rq35pHOWLuLzXL7pQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.208.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mysql": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.54.0.tgz", + "integrity": "sha512-bqC1YhnwAeWmRzy1/Xf9cDqxNG2d/JDkaxnqF5N6iJKN1eVWI+vg7NfDkf52/Nggp3tl1jcC++ptC61BD6738A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.208.0", + "@types/mysql": "2.15.27" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-pg": { + "version": "0.61.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.61.2.tgz", + "integrity": "sha512-l1tN4dX8Ig1bKzMu81Q1EBXWFRy9wqchXbeHDRniJsXYND5dC8u1Uhah7wz1zZta3fbBWflP2mJZcDPWNsAMRg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.208.0", + "@opentelemetry/semantic-conventions": "^1.34.0", + "@opentelemetry/sql-common": "^0.41.2", + "@types/pg": "8.15.6", + "@types/pg-pool": "2.0.6" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-redis": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.57.2.tgz", + "integrity": "sha512-vD1nzOUDOPjnvDCny7fmRSX/hMTFzPUCZKADF5tQ5DvBqlOEV/de/tOkwvIwo9YX956EBMT+8qSjhd7qPXFkRw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.208.0", + "@opentelemetry/redis-common": "^0.38.2", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-winston": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-winston/-/instrumentation-winston-0.53.0.tgz", + "integrity": "sha512-yF9v0DphyG715er1HG1pbweNUSygvc22xw2s2Y8E8oaEMJo2/nH3Ww/8c4K6gdI/6xvi2unla1KQBCYN4uCo8w==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "^0.208.0", + "@opentelemetry/instrumentation": "^0.208.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.208.0.tgz", + "integrity": "sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/otlp-transformer": "0.208.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.208.0.tgz", + "integrity": "sha512-fGvAg3zb8fC0oJAzfz7PQppADI2HYB7TSt/XoCaBJFi1mSquNUjtHXEoviMgObLAa1NRIgOC1lsV1OUKi+9+lQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/otlp-exporter-base": "0.208.0", + "@opentelemetry/otlp-transformer": "0.208.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base/node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.208.0.tgz", + "integrity": "sha512-DCFPY8C6lAQHUNkzcNT9R+qYExvsk6C5Bto2pbNxgicpcSWbe2WHShLxkOxIdNcBiYPdVHv/e7vH7K6TI+C+fQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.208.0", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/sdk-logs": "0.208.0", + "@opentelemetry/sdk-metrics": "2.2.0", + "@opentelemetry/sdk-trace-base": "2.2.0", + "protobufjs": "^7.3.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.2.0.tgz", + "integrity": "sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz", + "integrity": "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-b3": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-2.2.0.tgz", + "integrity": "sha512-9CrbTLFi5Ee4uepxg2qlpQIozoJuoAZU5sKMx0Mn7Oh+p7UrgCiEV6C02FOxxdYVRRFQVCinYR8Kf6eMSQsIsw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-b3/node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-jaeger": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.2.0.tgz", + "integrity": "sha512-FfeOHOrdhiNzecoB1jZKp2fybqmqMPJUXe2ZOydP7QzmTPYcfPeuaclTLYVhK3HyJf71kt8sTl92nV4YIaLaKA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-jaeger/node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/redis-common": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.38.3.tgz", + "integrity": "sha512-VCghU1JYs/4gP6Gqf/xro9MEsZ7LrMv2uONVsaESKL38ZOB9BqnI98FfS23wjMnHlpuE+TTaWSoAVNpTwYXzjw==", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + } + }, + "node_modules/@opentelemetry/resource-detector-azure": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-azure/-/resource-detector-azure-0.7.0.tgz", + "integrity": "sha512-aR2ALsK+b/+5lLDhK9KTK8rcuKg7+sqa/Cg+QCeasqoy7qby70FRtAbQcZGljJ5BLBcVPYjl1hcTYIUyL3Laww==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/resources": "^2.0.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.208.0.tgz", + "integrity": "sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.208.0", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz", + "integrity": "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.208.0.tgz", + "integrity": "sha512-pbAqpZ7zTMFuTf3YecYsecsto/mheuvnK2a/jgstsE5ynWotBjgF5bnz5500W9Xl2LeUfg04WMt63TWtAgzRMw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.208.0", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/exporter-logs-otlp-grpc": "0.208.0", + "@opentelemetry/exporter-logs-otlp-http": "0.208.0", + "@opentelemetry/exporter-logs-otlp-proto": "0.208.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "0.208.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.208.0", + "@opentelemetry/exporter-metrics-otlp-proto": "0.208.0", + "@opentelemetry/exporter-prometheus": "0.208.0", + "@opentelemetry/exporter-trace-otlp-grpc": "0.208.0", + "@opentelemetry/exporter-trace-otlp-http": "0.208.0", + "@opentelemetry/exporter-trace-otlp-proto": "0.208.0", + "@opentelemetry/exporter-zipkin": "2.2.0", + "@opentelemetry/instrumentation": "0.208.0", + "@opentelemetry/propagator-b3": "2.2.0", + "@opentelemetry/propagator-jaeger": "2.2.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/sdk-logs": "0.208.0", + "@opentelemetry/sdk-metrics": "2.2.0", + "@opentelemetry/sdk-trace-base": "2.2.0", + "@opentelemetry/sdk-trace-node": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/context-async-hooks": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.2.0.tgz", + "integrity": "sha512-qRkLWiUEZNAmYapZ7KGS5C4OmBLcP/H2foXeOEaowYCR0wi89fHejrfYfbuLVCMLp/dWZXKvQusdbUEZjERfwQ==", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.2.0.tgz", + "integrity": "sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz", + "integrity": "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/sdk-trace-node": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.2.0.tgz", + "integrity": "sha512-+OaRja3f0IqGG2kptVeYsrZQK9nKRSpfFrKtRBq4uh6nIB8bTBgaGvYQrQoRrQWQMA5dK5yLhDMDc0dvYvCOIQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/context-async-hooks": "2.2.0", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/sdk-trace-base": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", + "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.7.1.tgz", + "integrity": "sha512-pCpQxU68lV+I9s9svqMyVu5iHdDDUnqUpSxqwyCU8A9ejEsSnMPCbearwsUO4yk08ZJzAIUCFuReMdVQvHrdvg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/context-async-hooks": "2.7.1", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-web": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-web/-/sdk-trace-web-2.7.1.tgz", + "integrity": "sha512-K806OouCSOjMd8Nr7+ZCq3QT22tdAzzS/7h8vprfiKjkgFQ99/dvwU8d12WJANA6D5Qtme65hyBAqAu9CkQuxQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.40.0.tgz", + "integrity": "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/sql-common": { + "version": "0.41.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.41.2.tgz", + "integrity": "sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0" + } + }, + "node_modules/@opentelemetry/winston-transport": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/winston-transport/-/winston-transport-0.19.0.tgz", + "integrity": "sha512-MeG0fGNcpAhW9J9LiHgAJqIPySzj1xHCx4F+2R0ir4fzvm0ghKQRv6iUm3u1AhyKKJzDBeoHu7W98jJHNw8dnA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "^0.208.0", + "winston-transport": "4.*" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + } + }, + "node_modules/@redis/client": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.12.1.tgz", + "integrity": "sha512-7aPGWeqA3uFm43o19umzdl16CEjK/JQGtSXVPevplTaOU3VJA/rseBC1QvYUz9lLDIMBimc4SW/zrW4S89BaCA==", + "license": "MIT", + "dependencies": { + "cluster-key-slot": "1.1.2" + }, + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@node-rs/xxhash": "^1.1.0", + "@opentelemetry/api": ">=1 <2" + }, + "peerDependenciesMeta": { + "@node-rs/xxhash": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + } + } + }, + "node_modules/@redis/entraid": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@redis/entraid/-/entraid-5.12.1.tgz", + "integrity": "sha512-ygQ5W76LHGTG23bWxIzkqTStIjgrqq2Ltw9j1snrpnM5u/730SkEu+RXxt7JlpVKTJnM91+6yuh/ZK5VJkM6eA==", + "license": "MIT", + "dependencies": { + "@azure/identity": "^4.7.0", + "@azure/msal-node": "^2.16.1" + }, + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@redis/client": "^5.12.1" + } + }, + "node_modules/@redis/entraid/node_modules/@azure/msal-common": { + "version": "14.16.1", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.16.1.tgz", + "integrity": "sha512-nyxsA6NA4SVKh5YyRpbSXiMr7oQbwark7JU9LMeg6tJYTSPyAGkdx61wPT4gyxZfxlSxMMEyAsWaubBlNyIa1w==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@redis/entraid/node_modules/@azure/msal-node": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.16.3.tgz", + "integrity": "sha512-CO+SE4weOsfJf+C5LM8argzvotrXw252/ZU6SM2Tz63fEblhH1uuVaaO4ISYFuN4Q6BhTo7I3qIdi8ydUQCqhw==", + "license": "MIT", + "dependencies": { + "@azure/msal-common": "14.16.1", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@resvg/resvg-js": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js/-/resvg-js-2.6.2.tgz", + "integrity": "sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==", + "license": "MPL-2.0", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@resvg/resvg-js-android-arm-eabi": "2.6.2", + "@resvg/resvg-js-android-arm64": "2.6.2", + "@resvg/resvg-js-darwin-arm64": "2.6.2", + "@resvg/resvg-js-darwin-x64": "2.6.2", + "@resvg/resvg-js-linux-arm-gnueabihf": "2.6.2", + "@resvg/resvg-js-linux-arm64-gnu": "2.6.2", + "@resvg/resvg-js-linux-arm64-musl": "2.6.2", + "@resvg/resvg-js-linux-x64-gnu": "2.6.2", + "@resvg/resvg-js-linux-x64-musl": "2.6.2", + "@resvg/resvg-js-win32-arm64-msvc": "2.6.2", + "@resvg/resvg-js-win32-ia32-msvc": "2.6.2", + "@resvg/resvg-js-win32-x64-msvc": "2.6.2" + } + }, + "node_modules/@resvg/resvg-js-android-arm-eabi": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.6.2.tgz", + "integrity": "sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-android-arm64": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.6.2.tgz", + "integrity": "sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-darwin-arm64": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.6.2.tgz", + "integrity": "sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-darwin-x64": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.6.2.tgz", + "integrity": "sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-arm-gnueabihf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.6.2.tgz", + "integrity": "sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-arm64-gnu": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.6.2.tgz", + "integrity": "sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-arm64-musl": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.6.2.tgz", + "integrity": "sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-x64-gnu": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.6.2.tgz", + "integrity": "sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-x64-musl": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.6.2.tgz", + "integrity": "sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-win32-arm64-msvc": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.6.2.tgz", + "integrity": "sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-win32-ia32-msvc": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.6.2.tgz", + "integrity": "sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w==", + "cpu": [ + "ia32" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-win32-x64-msvc": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.6.2.tgz", + "integrity": "sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "license": "MIT" + }, + "node_modules/@types/archiver": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-3.1.1.tgz", + "integrity": "sha512-TzVZ9204sH1TuFylfr1cw/AA/3/VldAAXswEwKLXUOzA9mDg+m6gHF9EaqKNlozcjc6knX5m1KAqJzksPLSEfw==", + "license": "MIT", + "dependencies": { + "@types/glob": "*" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bunyan": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-1.8.11.tgz", + "integrity": "sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/color": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.7.tgz", + "integrity": "sha512-e/ecxz2EGJhUEaoeFa9Xx6ayIkBFQ5bwKgOWzmrEtP+ApYB5ou+eOB71DasFsn3qCKzIPrbi/NKdM8lI0tnGUQ==", + "license": "MIT", + "dependencies": { + "@types/color-convert": "1" + } + }, + "node_modules/@types/color-convert": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-1.9.0.tgz", + "integrity": "sha512-OKGEfULrvSL2VRbkl/gnjjgbbF7ycIlpSsX7Nkab4MOWi5XxmgBYvuiQ7lcCFY5cPDz7MUNaKgxte2VRmtr4Fg==", + "license": "MIT", + "dependencies": { + "@types/color-name": "*" + } + }, + "node_modules/@types/color-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-2.0.0.tgz", + "integrity": "sha512-63mTjolMJv75upGaUbT6J3lRDWl6pETPQsaWni9w3dMArhNBpgtHkX8ISb9zLV3YYLPA/SMk8ZGALa3k9WY/aQ==", + "license": "MIT" + }, + "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.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/escape-html": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/escape-html/-/escape-html-1.0.4.tgz", + "integrity": "sha512-qZ72SFTgUAZ5a7Tj6kf2SHLetiH5S6f8G5frB2SPQ3EyF02kxdyBFf4Tz4banE3xCgGnKgWLt//a6VuYHKYJTg==", + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/extract-zip": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@types/extract-zip/-/extract-zip-1.6.2.tgz", + "integrity": "sha512-ZPnENVnXrziwDKO8eaB/+I9SIA6CCFiJxEo4KD2PJ2M6ztWZCQu7u+AA6XNpGIGjqJshak8yeUPnb10V9oF4jg==", + "license": "MIT" + }, + "node_modules/@types/fs-extra": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", + "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==", + "license": "MIT", + "dependencies": { + "@types/jsonfile": "*", + "@types/node": "*" + } + }, + "node_modules/@types/gapi.client": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/gapi.client/-/gapi.client-1.0.8.tgz", + "integrity": "sha512-qJQUmmumbYym3Amax0S8CVzuSngcXsC1fJdwRS2zeW5lM63zXkw4wJFP+bG0jzgi0R6EsJKoHnGNVTDbOyG1ng==", + "license": "MIT" + }, + "node_modules/@types/gapi.client.androidpublisher": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/gapi.client.androidpublisher/-/gapi.client.androidpublisher-3.0.2.tgz", + "integrity": "sha512-ITizgxAt4cTHQ7n+pGRTHi/QeWA1MKYliiuZcuR2rjyiyAvR/jdTamKmUYxdtP3lWorR+q/+RFZToFKkCliGnQ==", + "deprecated": "use @types/gapi.client.androidpublisher-v3 instead; see https://github.com/Maxim-Mazurok/google-api-typings-generator/issues/652 for details", + "license": "MIT", + "dependencies": { + "@maxim_mazurok/gapi.client.androidpublisher-v3": "latest" + } + }, + "node_modules/@types/gapi.client.discovery-v1": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/gapi.client.discovery-v1/-/gapi.client.discovery-v1-0.0.4.tgz", + "integrity": "sha512-uevhRumNE65F5mf2gABLaReOmbFSXONuzFZjNR3dYv6BmkHg+wciubHrfBAsp3554zNo3Dcg6dUAlwMqQfpwjQ==", + "license": "MIT", + "dependencies": { + "@maxim_mazurok/gapi.client.discovery-v1": "latest" + } + }, + "node_modules/@types/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", + "license": "MIT", + "dependencies": { + "@types/minimatch": "^5.1.2", + "@types/node": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT" + }, + "node_modules/@types/inquirer": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.9.tgz", + "integrity": "sha512-/mWx5136gts2Z2e5izdoRCo46lPp5TMs9R15GTSsgg/XnZyxDWVqoVU3R9lWnccKpqwsJLvRoxbCjoJtZB7DSw==", + "license": "MIT", + "dependencies": { + "@types/through": "*", + "rxjs": "^7.2.0" + } + }, + "node_modules/@types/jsonfile": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", + "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/lodash": { + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", + "license": "MIT" + }, + "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/mime-types": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", + "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", + "license": "MIT" + }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "license": "MIT" + }, + "node_modules/@types/mysql": { + "version": "2.15.27", + "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.27.tgz", + "integrity": "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.19.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, + "node_modules/@types/pg": { + "version": "8.15.6", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.6.tgz", + "integrity": "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/pg-pool": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.6.tgz", + "integrity": "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==", + "license": "MIT", + "dependencies": { + "@types/pg": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", + "license": "MIT" + }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "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/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/through": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz", + "integrity": "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/tmp": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.6.tgz", + "integrity": "sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA==", + "license": "MIT" + }, + "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" + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, + "node_modules/@types/valid-url": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/valid-url/-/valid-url-1.0.7.tgz", + "integrity": "sha512-tgsWVG80dM5PVEBSbXUttPJTBCOo0IKbBh4R4z/SHsC5C81A3aaUH4fsbj+JYk7fopApU/Mao1c0EWTE592TSg==", + "license": "MIT" + }, + "node_modules/@typespec/ts-http-runtime": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.5.tgz", + "integrity": "sha512-yURCknZhvywvQItHMMmFSo+fq5arCUIyz/CVk7jD89MSai7dkaX8ufjCWp3NttLojoTVbcE72ri+be/TnEbMHw==", + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "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", + "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/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/aggregate-error": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-4.0.1.tgz", + "integrity": "sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==", + "license": "MIT", + "dependencies": { + "clean-stack": "^4.0.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/already": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/already/-/already-1.13.2.tgz", + "integrity": "sha512-GU0ZqMhSetZeDlivqttmAmd2UpCbPSucziaDJcCN2NdOTedzaJTqZZwHHuGJvp0Us1wzQG0vSqFqax1SqgH8Aw==", + "license": "MIT", + "dependencies": { + "throat": "^5.0.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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", + "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", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansi-styles/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", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ansi-styles/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" + }, + "node_modules/any-base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", + "license": "MIT" + }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "license": "MIT", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "license": "MIT" + }, + "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", + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "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" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "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.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bmp-js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", + "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "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.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "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/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callguard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callguard/-/callguard-2.0.0.tgz", + "integrity": "sha512-I3nd+fuj20FK1qu00ImrbH+II+8ULS6ioYr9igqR1xyqySoqc3DiHEyUM0mkoAdKeLGg2CtGnO8R3VRQX5krpQ==", + "license": "MIT" + }, + "node_modules/canvg": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-4.0.3.tgz", + "integrity": "sha512-fKzMoMBwus3CWo1Uy8XJc4tqqn98RoRrGV6CsIkaNiQT5lOeHuMh4fOt+LXLzn2Wqtr4p/c2TOLz4xtu4oBlFA==", + "license": "MIT", + "dependencies": { + "@types/raf": "^3.4.0", + "raf": "^3.4.1", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/centra": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/centra/-/centra-2.7.0.tgz", + "integrity": "sha512-PbFMgMSrmgx6uxCdm57RUos9Tc3fclMvhLSATYN39XsDV29B89zZ3KA89jmY0vwSGazyU+uerqwa6t+KaodPcg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "license": "MIT" + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "license": "MIT" + }, + "node_modules/clean-stack": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-4.2.0.tgz", + "integrity": "sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "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", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/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", + "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/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "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", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/concat-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "license": "MIT" + }, + "node_modules/cross-env": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", + "integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@epic-web/invariant": "^1.0.0", + "cross-spawn": "^7.0.6" + }, + "bin": { + "cross-env": "dist/bin/cross-env.js", + "cross-env-shell": "dist/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "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/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/del": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/del/-/del-7.1.0.tgz", + "integrity": "sha512-v2KyNk7efxhlyHpjEvfyxaAihKKK0nWCuf6ZtqZcFFpQRG0bJ12Qsr0RpvsICMjAAZ8DOVCxrlqpxISlMHC4Kg==", + "license": "MIT", + "dependencies": { + "globby": "^13.1.2", + "graceful-fs": "^4.2.10", + "is-glob": "^4.0.3", + "is-path-cwd": "^3.0.0", + "is-path-inside": "^4.0.0", + "p-map": "^5.5.0", + "rimraf": "^3.0.2", + "slash": "^4.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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", + "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/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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" + }, + "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.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "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.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "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", + "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/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==" + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "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.14.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/extract-zip": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", + "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", + "license": "BSD-2-Clause", + "dependencies": { + "concat-stream": "^1.6.2", + "debug": "^2.6.9", + "mkdirp": "^0.5.4", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-text-encoding": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", + "license": "Apache-2.0" + }, + "node_modules/fast-xml-builder": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.8.tgz", + "integrity": "sha512-sDVBc2gg8pSKvcbE8rBmOyjSGQf0AdsbqvHeIOv3D/uYNoV4eCReQXyDF8Pdv8+m1FHazACypSz2hR7O2S1LLw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.1.3" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.3.tgz", + "integrity": "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^2.1.0", + "fast-xml-builder": "^1.1.7", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.2.3" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/fetch-h2": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/fetch-h2/-/fetch-h2-2.5.1.tgz", + "integrity": "sha512-U+LQ+fHwF6TMg82A88wjljC5L174eoJfrc+0g4e7JWqL7U0w0QAoOkPDCGkO9KGH9BY55s4n45gLGOtlTAoqmw==", + "license": "MIT", + "dependencies": { + "@types/tough-cookie": "^4.0.0", + "already": "^1.13.1", + "callguard": "^2.0.0", + "get-stream": "^6.0.0", + "through2": "^4.0.2", + "to-arraybuffer": "^1.0.1", + "tough-cookie": "^4.0.0" + }, + "engines": { + "node": ">=10.4" + } + }, + "node_modules/file-type": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "license": "MIT", + "dependencies": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "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.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "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/forwarded-parse": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", + "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", + "license": "MIT" + }, + "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/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "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/gaxios": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", + "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", + "license": "Apache-2.0", + "dependencies": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.7" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gaxios/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", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/gaxios/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/gaxios/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", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gaxios/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/gaxios/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/gcp-metadata": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", + "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "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", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gifwrap": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz", + "integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==", + "license": "MIT", + "dependencies": { + "image-q": "^4.0.0", + "omggif": "^1.0.10" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "license": "MIT", + "dependencies": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "license": "MIT", + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/google-auth-library": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz", + "integrity": "sha512-5Rk7iLNDFhFeBYc3s8l1CqzbEBcdhwR193RlD4vSNFajIcINKI8W8P0JLmBpwymHqqWbX34pJDQu39cSy/6RsA==", + "license": "Apache-2.0", + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/google-p12-pem": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.4.tgz", + "integrity": "sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg==", + "deprecated": "Package is no longer maintained", + "license": "MIT", + "dependencies": { + "node-forge": "^1.3.1" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/googleapis": { + "version": "81.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-81.0.0.tgz", + "integrity": "sha512-PV4A3PWbOFkm44k4jk1I4ixFsfwc9dZWthfyGkU3322l0HMx2AYIuYqqN9vqQPlD734fTz4MOetETtQ0pH4KTg==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^7.0.2", + "googleapis-common": "^5.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/googleapis-common": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-5.1.0.tgz", + "integrity": "sha512-RXrif+Gzhq1QAzfjxulbGvAY3FPj8zq/CYcvgjzDbaBNCD6bUl+86I7mUs4DKWHGruuK26ijjR/eDpWIDgNROA==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^4.0.0", + "google-auth-library": "^7.14.0", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "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/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/gtoken": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", + "integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==", + "license": "MIT", + "dependencies": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "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" + }, + "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.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "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/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-q": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", + "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", + "license": "MIT", + "dependencies": { + "@types/node": "16.9.1" + } + }, + "node_modules/image-q/node_modules/@types/node": { + "version": "16.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", + "license": "MIT" + }, + "node_modules/import-in-the-middle": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.6.tgz", + "integrity": "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.15.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "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/inquirer": { + "version": "9.3.8", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.3.8.tgz", + "integrity": "sha512-pFGGdaHrmRKMh4WoDDSowddgjT1Vkl90atobmTeSmcPGdYiwikch/m/Ef5wRaiamHejtw0cUUMMerzDUXCci2w==", + "license": "MIT", + "dependencies": { + "@inquirer/external-editor": "^1.0.2", + "@inquirer/figures": "^1.0.3", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "1.0.0", + "ora": "^5.4.1", + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "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-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", + "license": "MIT" + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-3.0.0.tgz", + "integrity": "sha512-kyiNFFLU0Ampr6SDZitD/DwUo4Zs1nSdnygUBqsu3LooL00Qvb5j+UnvApUn/TTj1J3OuE6BTdQ5rudKmU2ZaA==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-path-inside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, + "node_modules/isomorphic-fetch/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/jimp": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.22.12.tgz", + "integrity": "sha512-R5jZaYDnfkxKJy1dwLpj/7cvyjxiclxU3F4TrI/J4j2rS0niq6YDUMoPn5hs8GDpO+OZGo7Ky057CRtWesyhfg==", + "license": "MIT", + "dependencies": { + "@jimp/custom": "^0.22.12", + "@jimp/plugins": "^0.22.12", + "@jimp/types": "^0.22.12", + "regenerator-runtime": "^0.13.3" + } + }, + "node_modules/jpeg-js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", + "license": "BSD-3-Clause" + }, + "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/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "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/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.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/load-bmfont": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.2.tgz", + "integrity": "sha512-qElWkmjW9Oq1F9EI5Gt7aD9zcdHb9spJCW1L/dmPf7KzCCEJxq8nhHz5eCgI9aMf7vrG/wyaCqdsI+Iy9ZTlog==", + "license": "MIT", + "dependencies": { + "buffer-equal": "0.0.1", + "mime": "^1.3.4", + "parse-bmfont-ascii": "^1.0.3", + "parse-bmfont-binary": "^1.0.5", + "parse-bmfont-xml": "^1.1.4", + "phin": "^3.7.1", + "xhr": "^2.0.1", + "xtend": "^4.0.0" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "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" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "license": "MIT" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "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/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/logform/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/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "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/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "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/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "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/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "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/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/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/min-document": { + "version": "2.19.2", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.2.tgz", + "integrity": "sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==", + "license": "MIT", + "dependencies": { + "dom-walk": "^0.1.0" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", + "license": "MIT" + }, + "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/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "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-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-forge": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.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-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/omggif": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==", + "license": "MIT" + }, + "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", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-5.5.0.tgz", + "integrity": "sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==", + "license": "MIT", + "dependencies": { + "aggregate-error": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==", + "license": "MIT" + }, + "node_modules/parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==", + "license": "MIT" + }, + "node_modules/parse-bmfont-xml": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz", + "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==", + "license": "MIT", + "dependencies": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.5.0" + } + }, + "node_modules/parse-headers": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.6.tgz", + "integrity": "sha512-Tz11t3uKztEW5FEVZnj1ox8GKblWn+PvHY9TmJV5Mll2uHEwRdR/5Li1OlXoECjLYkApdhWy44ocONwXLiKO5A==", + "license": "MIT" + }, + "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/password-generator": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/password-generator/-/password-generator-2.3.2.tgz", + "integrity": "sha512-kJWUrdveSAqHCeWJWnv5vNc89hFHM5au+pvKja5+xCTxlRF3zQaecJlR6hSoOotAJtQ3otQq4/Q4iWc/TxsXhA==", + "bin": { + "password-generator": "bin/password-generator" + } + }, + "node_modules/path-expression-matcher": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "license": "MIT" + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz", + "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/phin": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/phin/-/phin-3.7.1.tgz", + "integrity": "sha512-GEazpTWwTZaEQ9RhL7Nyz0WwqilbqgLahDM3D0hxWwmVDI52nXEybHqiN6/elwpkJBhcuj+WbBu+QfT0uhPGfQ==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT", + "dependencies": { + "centra": "^2.7.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pixelmatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", + "integrity": "sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==", + "license": "ISC", + "dependencies": { + "pngjs": "^3.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, + "node_modules/pixelmatch/node_modules/pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "license": "MIT", + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/protobufjs": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.0.3.tgz", + "integrity": "sha512-LBYnMWkKLB8fE/ljROPDbCl7mgLSlI+oBe1fAAr5MTqFg4TIi0tYrVVurJvQggOjnUYMQtEZBjrej59ojMNTHQ==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@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.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "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/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "license": "MIT", + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "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.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "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", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readable-web-to-node-stream": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", + "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^4.7.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/readable-web-to-node-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, + "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", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-in-the-middle": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", + "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3" + }, + "engines": { + "node": ">=9.3.0 || >=8.10.0 <9.0.0" + } + }, + "node_modules/require-in-the-middle/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/require-in-the-middle/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/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "license": "MIT OR SEE LICENSE IN FEEL-FREE.md", + "engines": { + "node": ">= 0.8.15" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "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", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "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/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "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/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stackblur-canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", + "license": "MIT", + "engines": { + "node": ">=0.1.14" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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", + "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", + "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", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strnum": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", + "integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/strtok3": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/tar": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", + "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "license": "MIT" + }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "license": "MIT", + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/timm": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz", + "integrity": "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==", + "license": "MIT" + }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, + "node_modules/tldts": { + "version": "7.0.30", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.30.tgz", + "integrity": "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw==", + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.30" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.30", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.30.tgz", + "integrity": "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==", + "license": "MIT" + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==", + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "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/token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "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/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/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-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "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/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", + "license": "BSD" + }, + "node_modules/utif2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", + "integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==", + "license": "MIT", + "dependencies": { + "pako": "^1.0.11" + } + }, + "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" + }, + "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": "14.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", + "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "license": "MIT" + }, + "node_modules/valid-url": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", + "integrity": "sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA==" + }, + "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/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 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/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "license": "MIT" + }, + "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/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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" + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "license": "MIT", + "dependencies": { + "global": "~4.4.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==", + "license": "MIT" + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "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", + "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", + "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", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "license": "MIT", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + } + } +} diff --git a/apps/pwabuilder-meta-horizon/package.json b/apps/pwabuilder-meta-horizon/package.json new file mode 100644 index 000000000..2aa966e10 --- /dev/null +++ b/apps/pwabuilder-meta-horizon/package.json @@ -0,0 +1,78 @@ +{ + "name": "@pwabuilder/cloudmetahorizon", + "version": "1.0.0", + "description": "Web service that packages a Progressive Web App as an Android Trusted Web Activity app for publishing in the Meta Horizon Store.", + "main": "server.js", + "type": "module", + "scripts": { + "start": "node ./server.js", + "postinstall": "tsc", + "dev": "cross-env NODE_ENV=development node --inspect=5859 --loader ts-node/esm ./server.ts", + "build": "tsc --noEmitOnError", + "docker:build": "npm run build && docker build -t cloud-meta-horizon .", + "docker:run": "docker run -p 5780:5858 -e NODE_ENV=development cloud-meta-horizon" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/pwa-builder/PWABuilder.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/pwa-builder/pwabuilder/issues" + }, + "homepage": "https://github.com/pwa-builder/PWABuilder/", + "dependencies": { + "@azure/identity": "^4.13.0", + "@azure/monitor-opentelemetry": "^1.7.0", + "@azure/storage-blob": "^12.29.0", + "@meta-quest/bubblewrap-core": "^1.24.1", + "@opentelemetry/api": "^1.9.0", + "@redis/client": "^5.10.0", + "@redis/entraid": "^5.10.0", + "@types/archiver": "^3.0.0", + "@types/cors": "^2.8.13", + "@types/escape-html": "^1.0.4", + "@types/express": "^4.17.2", + "@types/fs-extra": "^11.0.4", + "@types/tmp": "^0.2.6", + "archiver": "^5.3.2", + "body-parser": "^1.20.4", + "cors": "^2.8.5", + "del": "^7.1.0", + "dotenv": "^16.6.1", + "escape-html": "^1.0.3", + "express": "^4.22.1", + "fs-extra": "^11.3.3", + "node-fetch": "^3.3.2", + "password-generator": "^2.3.2", + "tmp": "^0.2.5", + "ts-node": "^10.9.2", + "typescript": "^5.9.3" + }, + "devDependencies": { + "cross-env": "^10.1.0" + }, + "overrides": { + "tough-cookie": ">=4.1.3", + "handlebars": ">=4.7.7", + "json-schema": ">=0.4.0", + "protobufjs": ">=7.3.2", + "google-protobuf": ">=3.21.9", + "ws": ">=8.17.1", + "tar-fs": ">=3.0.6", + "form-data": ">=4.0.4", + "node-uuid": ">=1.4.4", + "uuid": ">=8.3.2", + "node-forge": ">=0.7.2", + "qs": ">=6.3.2", + "path-parse": ">=1.0.7", + "fstream": ">=1.0.12", + "dot-prop": ">=5.1.1", + "hoek": ">=5.0.3", + "vm2": ">=3.9.8", + "async": ">=3.2.2", + "hawk": ">=9.0.1", + "minimatch": ">=3.0.2" + } +} diff --git a/apps/pwabuilder-meta-horizon/routes/project.ts b/apps/pwabuilder-meta-horizon/routes/project.ts new file mode 100644 index 000000000..635a240fe --- /dev/null +++ b/apps/pwabuilder-meta-horizon/routes/project.ts @@ -0,0 +1,516 @@ +import express from 'express'; +import escape from 'escape-html'; +import { MetaHorizonPackageOptions } from '../models/metaHorizonPackageOptions.js'; +import tmp from 'tmp'; +import net from 'net'; +import { SigningOptions, } from '../models/signingOptions.js'; +import { AppPackageRequest } from '../models/appPackageRequest.js'; +import generatePassword from 'password-generator'; +import fetch, { Response } from 'node-fetch'; +import { AnalyticsInfo, trackEvent } from '../services/analytics.js'; +import { PackageCreator } from '../services/packageCreator.js'; +import { PackageCreationProgress } from '../models/packageCreationProgress.js'; +import { errorToString } from "../utils/errorToString.js"; +import { packageJobQueue } from "../services/packageJobQueue.js"; +import { database } from "../services/redisService.js"; +import { MetaHorizonPackageJob } from "../models/metaHorizonPackageJob.js"; +import { blobStorage } from "../services/azureStorageBlobService.js"; + +const router = express.Router(); + +tmp.setGracefulCleanup(); // remove any tmp file artifacts on process exit +const packageCreator = new PackageCreator(); +packageCreator.addEventListener("progress", e => packageCreationProgress(e)); + +/** + * Health check endpoint that verifies the service is running. + * This is used by Azure health checks to ensure app instance is healthy. + */ +router.get('/ping', (_: express.Request, response: express.Response) => { + response.status(200).json({ status: 'healthy' }); +}); + +/** + * Health check endpoint that returns the current package job queue length. + * Useful for monitoring and diagnostics. + */ +router.get('/health', async (_: express.Request, response: express.Response) => { + try { + const queueLength = await packageJobQueue.getQueueLength(); + const processedLastHour = await packageJobQueue.getProcessedCountLastHour(); + const runningTimeMs = Date.now() - packageJobQueue.getStartDate().getTime(); + const twoHoursInMs = 2 * 60 * 60 * 1000; + let errorMessage = ""; + if (queueLength >= 50) { + errorMessage = `Meta Horizon package job queue length is high: ${queueLength} jobs are currently in the queue.`; + } else if (queueLength > 5 && processedLastHour < 1 && runningTimeMs > twoHoursInMs) { + errorMessage = `Meta Horizon package jobs processed in the last hour is low: only ${processedLastHour} jobs were processed in the previous 60 minutes by the current instance.`; + } + + const totalSeconds = Math.floor(runningTimeMs / 1000); + const hours = Math.floor(totalSeconds / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60); + const seconds = totalSeconds % 60; + const numFormat = new Intl.NumberFormat('en', { minimumIntegerDigits: 2 }); + const runningTime = `${numFormat.format(hours)}:${numFormat.format(minutes)}:${numFormat.format(seconds)}`; + const statusCode = errorMessage ? 500 : 200; + response.status(statusCode).json({ + status: 'healthy', + metaHorizonPackageQueueLength: queueLength, + metaHorizonPackagesProcessedInLastHour: processedLastHour, + runningTime: runningTime, + errorMessage: errorMessage + }); + } catch (error) { + console.error("Error getting queue length for health check", error); + response.status(500).json({ status: 'unhealthy', error: 'Failed to retrieve queue length' }); + } +}); + +/** + * Generates an APK package and zips it up along with the signing key info. Sends back the zip file. + * + * NOTE: This endpoint is deprecated. Fetching this endpoint can take too long. Instead, use /enqueuePackageJob to enqueue a background job. + */ +router.post(['/generateAppPackage', '/generateApkZip'], async (request, response) => await generatePackageZip(request, response)); + +/** + * Enqueues a Meta Horizon packaging job to be processed in the background. + */ +router.post("/enqueuePackageJob", async (request, response) => await enqueuePackage(request, response)); + +/** + * Gets the package job, including status, of the job with the given ID. + */ +router.get("/getPackageJob", async (request, response) => await getPackageJob(request, response)); + +/** + * Downloads the zip file for the completed package job with the given ID. + */ +router.get("/downloadPackageZip", async (request, response) => await downloadPackageZip(request, response)); + +/** + * This endpoint tries to fetch a URL. This is useful because we occasionally have bug reports + * where the packaging service can't fetch an image or other resource. + */ +router.get('/fetch', async (request, response) => await fetchResource(request, response)); + +async function generatePackageZip(request: express.Request, response: express.Response): Promise { + console.info("Received Meta Horizon app package request"); + const packageRequest = validateMetaHorizonOptionsRequest(request); + const platformId = request.headers['platform-identifier']; + const platformIdVersion = request.headers['platform-identifier-version']; + const correlationId = request.headers['correlation-id']; + const analyticsInfo: AnalyticsInfo = { + url: packageRequest.options?.pwaUrl || packageRequest.options?.host || '', + packageId: packageRequest.options?.packageId || '', + name: packageRequest.options?.name || '', + platformId: platformId ? platformId.toString() : null, + platformIdVersion: platformIdVersion + ? platformIdVersion.toString() + : null, + correlationId: correlationId ? correlationId.toString() : null, + referrer: request.query.ref ? request.query.ref.toString() : null, + }; + + if (packageRequest.validationErrors.length > 0 || !packageRequest.options) { + const errorMessage = 'Invalid PWA settings: ' + packageRequest.validationErrors.join(', '); + console.error("Package request was invalid", errorMessage); + trackEvent(analyticsInfo, errorMessage, false); + response.status(500).send(escape(errorMessage)); + return; + } + + try { + console.info("Generating Meta Horizon app package..."); + const zipFile = await packageCreator.createZip(packageRequest.options); + response.sendFile(zipFile, {}); + trackEvent(analyticsInfo, null, true); + console.info('Package generation completed successfully.'); + } catch (err) { + console.error('Error generating Meta Horizon app package', err); + const errorString = errorToString(err); + trackEvent(analyticsInfo, errorString, false); + response + .status(500) + .send('Error generating app package: \r\n' + escape(errorString)); + } +} + +async function downloadPackageZip(request: express.Request, response: express.Response): Promise { + const jobId = request.query.id; + if (!jobId || typeof jobId !== 'string') { + response.status(400).send('You must specify a jobId query parameter'); + return; + } + + const job = await database.getJson(jobId); + if (!job) { + console.warn("No job found with ID", jobId); + response.status(404).send(`No job found with ID`); + return; + } + + if (job.status === "Failed") { + response.status(400).send(`Job failed: ${job.errors || "Unknown error"}`); + return; + } + + if (job.status !== "Completed" || !job.uploadedBlobFileName) { + response.status(400).send(`Job is not ready for download. Current status: ${job.status}`); + return; + } + + const downloadStream = await blobStorage.downloadFileStream(job.uploadedBlobFileName); + downloadStream.on('error', (err) => { + console.error("Download zip file failed due to stream error:", err); + if (!response.headersSent) { + response.status(500).send("Download failed due to a stream error."); + } + }); + + const hostname = new URL(job.pwaUrl).host.replace(/[^a-zA-Z0-9.-]/g, '_'); + const fileName = `${hostname} - Meta Horizon Package.zip`; + response.setHeader('Content-Disposition', `attachment; filename="${fileName}"`); + response.setHeader('Content-Type', 'application/zip'); + downloadStream.pipe(response); +} + +async function getPackageJob(request: express.Request, response: express.Response): Promise { + const jobId = request.query.id; + if (!jobId || typeof jobId !== 'string') { + response.status(400).send('You must specify a jobId query parameter'); + return; + } + + console.info("Received request for package job status", jobId); + + const job = await database.getJson(jobId); + if (!job) { + console.warn("No job found with ID", jobId); + response.status(404).send(`No job found with ID`); + return; + } + + console.info("Request for package job completed successfully. Returning job", jobId, job.status); + response.status(200).json(job); +} + +async function enqueuePackage(request: express.Request, response: express.Response): Promise { + console.info("Received Meta Horizon app package request"); + const packageRequest = validateMetaHorizonOptionsRequest(request); + const platformId = request.headers['platform-identifier']; + const platformIdVersion = request.headers['platform-identifier-version']; + const correlationId = request.headers['correlation-id']; + const analyticsInfo: AnalyticsInfo = { + url: packageRequest.options?.pwaUrl || packageRequest.options?.host || '', + packageId: packageRequest.options?.packageId || '', + name: packageRequest.options?.name || '', + platformId: platformId ? platformId.toString() : null, + platformIdVersion: platformIdVersion + ? platformIdVersion.toString() + : null, + correlationId: correlationId ? correlationId.toString() : null, + referrer: request.query.ref ? request.query.ref.toString() : null, + }; + + if (packageRequest.validationErrors.length > 0 || !packageRequest.options) { + const errorMessage = 'Invalid PWA settings: ' + packageRequest.validationErrors.join(', '); + console.error("Package request was invalid", errorMessage); + trackEvent(analyticsInfo, errorMessage, false); + response.status(500).send(escape(errorMessage)); + return; + } + + try { + const packageOptions = packageRequest.options; + packageOptions.analyticsInfo = analyticsInfo; + const jobId = await packageJobQueue.enqueue(packageOptions); + + console.info(`Package job enqueued with ID ${jobId} for ${packageOptions.analysisId}`); + response.status(200).send(jobId); + } catch (error) { + console.error("Failed to enqueue package job:", error); + trackEvent(analyticsInfo, errorToString(error), false); + response.status(500).send("Failed to enqueue package job"); + } +} + +async function fetchResource(request: express.Request, response: express.Response) { + const url = request.query.url; + if (!url) { + response.status(500).send('You must specify a URL'); + return; + } + if (typeof url !== 'string') { + response.status(500).send('URL must be a string'); + return; + } + + let type; + if ( + request.query.type !== null && + typeof request.query.type === 'string' && + ['blob', 'json', 'text'].includes(request.query.type) + ) { + type = request.query.type; + } else { + type = 'text'; + } + + // SSRF PREVENTION: validate the URL before fetching! + let parsedUrl: URL; + try { + parsedUrl = new URL(url); + } catch (parseError) { + response.status(400).send('Invalid URL'); + return; + } + + if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') { + response.status(400).send('Only HTTP/HTTPS URLs are allowed'); + return; + } + + const forbiddenHostnames = [ + 'localhost', + '127.0.0.1', + '0.0.0.0', + '::1', + '[::1]' + ]; + const hostname = parsedUrl.hostname.toLowerCase(); + if (forbiddenHostnames.includes(hostname)) { + response.status(403).send('Access to localhost is forbidden'); + return; + } + function isPrivateIp(ip: string) { + if (net.isIP(ip)) { + // IPv4 + if (ip.startsWith('10.')) return true; + if (ip.startsWith('192.168.')) return true; + if (ip.startsWith('172.')) { + const second = Number(ip.split('.')[1]); + if (second >= 16 && second <= 31) return true; + } + if (ip === '127.0.0.1' || ip === '0.0.0.0') return true; + } + // IPv6 + if (ip === '::1') return true; + if (ip.startsWith('fc') || ip.startsWith('fd')) return true; + return false; + } + + if (net.isIP(hostname) && isPrivateIp(hostname)) { + response.status(403).send('Access to private IP ranges is forbidden'); + return; + } + + let fetchResult: Response; + + try { + console.info("Fetching URL", parsedUrl); + // @sarif-suppress 195 Justification: while this URL is user-provided, we check it for SSRF above. + fetchResult = await fetch(parsedUrl); + } catch (fetchError) { + console.error("Unable to fetch URL", url, fetchError); + response + .status(500) + .send(`Unable to initiate fetch for ${escape(url)}. Error: ${escape(String(fetchError))}`); + return; + } + + if (!fetchResult.ok) { + response + .status(fetchResult.status) + .send( + `Unable to fetch ${escape(url)}. Status: ${fetchResult.status}, ${escape(String(fetchResult.statusText))}` + ); + return; + } + + if (fetchResult.type) { + response.type(fetchResult.type); + } + + if (fetchResult.headers) { + fetchResult.headers.forEach((value, name) => + response.setHeader(name, value) + ); + } + + try { + if (type === 'blob') { + const blob = await fetchResult.arrayBuffer(); + response.status(fetchResult.status).send(Buffer.from(blob)); + } else if (type === 'json') { + const json = await fetchResult.json(); + response.status(fetchResult.status).send(json); + } else { + const text = await fetchResult.text(); + response.status(fetchResult.status).send(text); + } + } catch (getResultError) { + response + .status(500) + .send( + `Unable to fetch result from ${escape(url)} using type ${escape(type)}. Error: ${escape(String(getResultError))}` + ); + } +} + +function validateMetaHorizonOptionsRequest(request: express.Request): AppPackageRequest { + const validationErrors: string[] = []; + + let options: MetaHorizonPackageOptions | null = + tryParseOptionsFromRequest(request); + if (!options) { + validationErrors.push( + "Malformed argument. Coudn't find MetaHorizonPackageOptions in body" + ); + return { + options: null, + validationErrors, + }; + } + + // Required fields. Meta Horizon also always requires fullScopeUrl since it targets Quest. + const requiredFields: Array = [ + 'appVersion', + 'appVersionCode', + 'backgroundColor', + 'display', + 'fallbackType', + 'fullScopeUrl', + 'host', + 'iconUrl', + 'launcherName', + 'navigationColor', + 'packageId', + 'signingMode', + 'startUrl', + 'themeColor', + 'webManifestUrl', + ]; + + validationErrors.push( + ...requiredFields.filter((f) => !options![f]).map((f) => `${f as string} is required`) + ); + + // Meta Horizon Store in-app purchases require a numeric Meta Horizon application ID from the + // Meta Developer Dashboard (it's emitted as OCULUS_APP_ID in the AndroidManifest). Without it, + // the Horizon Billing feature can't talk to the Store at runtime. + if (options.features?.horizonBilling?.enabled && !options.metaHorizonAppId) { + validationErrors.push("metaHorizonAppId is required when features.horizonBilling.enabled is true"); + } + if (options.metaHorizonAppId && !/^\d+$/.test(options.metaHorizonAppId)) { + validationErrors.push("metaHorizonAppId must be a numeric string (the app ID from Meta Developer Dashboard)"); + } + if (options.horizonOSAppMode && options.horizonOSAppMode !== '2D' && options.horizonOSAppMode !== 'immersive') { + validationErrors.push("horizonOSAppMode must be either '2D' or 'immersive'"); + } + + // Ensure webManifestUrl is an absolute HTTPS URL. + if (options.webManifestUrl) { + try { + const manifestUrl = new URL(options.webManifestUrl, options.fullScopeUrl); + if (manifestUrl.protocol !== 'https:') { + validationErrors.push('webManifestUrl must be an absolute HTTPS URL'); + } + } catch (manifestUrlError) { + validationErrors.push('webManifestUrl must be an absolute HTTPS URL'); + } + } + + if (options.signingMode !== 'none' && !options.signing) { + validationErrors.push( + `Signing options are required when signing mode = '${options.signingMode}'` + ); + } + + if (options.signingMode === 'mine' && options.signing) { + if (!options.signing.file) { + validationErrors.push( + "You must supply a signing key file when signing mode = 'mine'" + ); + } + + if (options.signing.file && !options.signing.file.startsWith('data:')) { + validationErrors.push( + 'Signing file must be a base64 encoded string containing the Android keystore file' + ); + } + + if (!options.signing.storePassword) { + validationErrors.push( + "You must supply a store password when signing mode = 'mine'" + ); + } + + if (!options.signing.keyPassword) { + validationErrors.push( + "You must supply a key password when signing mode = 'mine'" + ); + } + } + + if (options.signingMode !== 'none' && options.signing) { + console.log("options.signing", options.signing); + const passToUse = generatePassword(12, false); + + if (!options.signing.keyPassword) { + options.signing.keyPassword = passToUse; + } + if (!options.signing.storePassword) { + options.signing.storePassword = passToUse; + } + + const requiredSigningOptions: Array = [ + 'alias', + 'keyPassword', + 'storePassword', + ]; + + if (options.signingMode === 'new') { + requiredSigningOptions.push( + 'countryCode', + 'fullName', + 'organization', + 'organizationalUnit' + ); + } + + validationErrors.push( + ...requiredSigningOptions + .filter((f) => !options?.signing![f]) + .map((f) => `Signing option ${f as string} is required`) + ); + } + + return { + options: options, + validationErrors, + }; +} + +function tryParseOptionsFromRequest( + request: express.Request +): MetaHorizonPackageOptions | null { + if (request.body['packageId']) { + return request.body as MetaHorizonPackageOptions; + } + + return null; +} + +function packageCreationProgress(e: PackageCreationProgress) { + if (e.level === 'error') { + console.error(e.message); + } else if (e.level === 'warn') { + console.warn(e.message); + } else { + console.info(e.message); + } +} + +export default router; diff --git a/apps/pwabuilder-meta-horizon/server.ts b/apps/pwabuilder-meta-horizon/server.ts new file mode 100644 index 000000000..25388c0d5 --- /dev/null +++ b/apps/pwabuilder-meta-horizon/server.ts @@ -0,0 +1,51 @@ +// CRITICAL: Disable Azure SDK OpenTelemetry instrumentation BEFORE any imports +// to prevent conflicts with Azure Monitor OpenTelemetry +process.env.AZURE_TRACING_DISABLED = 'true'; +process.env.APPLICATIONINSIGHTS_NO_AZURE_INSTRUMENTATION = 'true'; +// Disable Azure Monitor internal metrics that can cause periodic shutdown issues +process.env.APPLICATIONINSIGHTS_NO_STATSBEAT = 'true'; +process.env.APPLICATIONINSIGHTS_STATSBEAT_DISABLED = 'true'; +process.env.APPLICATIONINSIGHTS_NO_DIAGNOSTIC_CHANNEL = 'true'; + +import dotenv from 'dotenv'; +import app from './app.js'; + +// Load environment variables FIRST before any OpenTelemetry setup +const configResult = dotenv.config({ + path: `./env/${app.get("env")}.env` +}); +if (configResult.error) { + console.error("Error loading .env file", configResult.error); +} else { + console.info("Configured with environment file", configResult.parsed); +} + +// Now setup Azure Monitor OpenTelemetry after environment is configured +import { setupAnalytics } from "./services/analytics.js"; +setupAnalytics(); + +// Add global error handlers for diagnostics +process.on('uncaughtException', (err) => { + console.error('Uncaught Exception:', err); +}); +process.on('unhandledRejection', (reason) => { + console.error('Unhandled Rejection:', reason); +}); + +const port = process.env.PORT || 5859; + +const jdk8Path = process.env.JDK8PATH; +const androidDevToolsPath = process.env.ANDROIDTOOLSPATH; +if (!jdk8Path || !androidDevToolsPath) { + console.error("Couldn't find environment variables for JDK8 path or Android Dev tools", app.get("env"), jdk8Path, androidDevToolsPath); +} + +// Kick off our background job processor. +// This periodically polls Redis for new Meta Horizon packaging jobs and processes them. +import { PackageJobProcessor } from './services/packageJobProcessor.js'; +const jobProcessor = new PackageJobProcessor(); +jobProcessor.start(); + +app.listen(port, () => { + console.log(`App listening on port ${port}`); +}); diff --git a/apps/pwabuilder-meta-horizon/services/analytics.ts b/apps/pwabuilder-meta-horizon/services/analytics.ts new file mode 100644 index 000000000..314730a5c --- /dev/null +++ b/apps/pwabuilder-meta-horizon/services/analytics.ts @@ -0,0 +1,148 @@ +import { useAzureMonitor } from '@azure/monitor-opentelemetry'; +import { trace, metrics } from '@opentelemetry/api'; + +enum AnalyticsStatus { + ENABLED = 1, + DISABLED = 0, + DEFAULT = -1, +} + +let analyticsStatus: AnalyticsStatus = AnalyticsStatus.DEFAULT; +let tracer: any = null; +let meter: any = null; + +export function setupAnalytics() { + try { + // Initialize Azure Monitor OpenTelemetry + const connectionString = process.env.APPLICATIONINSIGHTS_CONNECTION_STRING; + + if (connectionString) { + console.log('Setting up Azure Monitor OpenTelemetry...'); + + // Additional environment variables to prevent Azure SDK conflicts + process.env.AZURE_TRACING_DISABLED = 'true'; + process.env.APPLICATIONINSIGHTS_NO_AZURE_INSTRUMENTATION = 'true'; + + // Disable Azure Monitor internal metrics that can cause shutdown conflicts + process.env.APPLICATIONINSIGHTS_NO_STATSBEAT = 'true'; + process.env.APPLICATIONINSIGHTS_STATSBEAT_DISABLED = 'true'; + process.env.APPLICATIONINSIGHTS_NO_DIAGNOSTIC_CHANNEL = 'true'; + + // Configure Azure Monitor with OpenTelemetry + try { + useAzureMonitor({ + azureMonitorExporterOptions: { + connectionString, + }, + samplingRatio: 1.0, + }); + console.log('Azure Monitor useAzureMonitor() completed successfully'); + } catch (azureMonitorError) { + console.warn('Error in useAzureMonitor, but continuing:', azureMonitorError); + } + // Create tracer and meter for custom telemetry + tracer = trace.getTracer('pwabuilder-meta-horizon', '1.0.0'); + meter = metrics.getMeter('pwabuilder-meta-horizon', '1.0.0'); + + analyticsStatus = AnalyticsStatus.ENABLED; + console.log('Azure Monitor OpenTelemetry enabled successfully'); + } else { + console.warn('APPLICATIONINSIGHTS_CONNECTION_STRING not found, analytics disabled'); + analyticsStatus = AnalyticsStatus.DISABLED; + } + } catch (e) { + analyticsStatus = AnalyticsStatus.DISABLED; + const error = e as Error; + if (error.message && error.message.includes('duplicate registration')) { + console.warn("OpenTelemetry API already registered (likely by Azure SDK), continuing with existing registration..."); + try { + tracer = trace.getTracer('pwabuilder-meta-horizon', '1.0.0'); + meter = metrics.getMeter('pwabuilder-meta-horizon', '1.0.0'); + analyticsStatus = AnalyticsStatus.ENABLED; + console.log('Using existing OpenTelemetry registration'); + } catch (getError) { + console.warn("Could not get existing OpenTelemetry providers:", getError); + } + } else { + console.warn("Azure Monitor OpenTelemetry couldn't be enabled", e); + } + } +} + +export function trackEvent( + analyticsInfo: AnalyticsInfo, + error: string | null, + success: boolean +) { + if (analyticsStatus === AnalyticsStatus.DEFAULT) { + setupAnalytics(); + } + + if (analyticsStatus === AnalyticsStatus.DISABLED || !tracer) { + console.warn("Analytics disabled, skipping event tracking"); + return; + } + + try { + const eventName = success ? 'MetaHorizonPackageEvent' : 'MetaHorizonPackageFailureEvent'; + + // Create a span for the event + const span = tracer.startSpan(eventName, { + attributes: { + 'pwa.name': analyticsInfo.name, + 'pwa.url': analyticsInfo.url, + 'pwa.platform_id': analyticsInfo.platformId || 'unknown', + 'pwa.platform_version': analyticsInfo.platformIdVersion || 'unknown', + 'pwa.referrer': analyticsInfo.referrer || 'unknown', + 'pwa.success': success, + 'pwa.package_id': analyticsInfo.packageId, + } + }); + + // Add correlation ID if available + if (analyticsInfo.correlationId) { + span.setAttributes({ + 'pwa.correlation_id': analyticsInfo.correlationId + }); + } + + // Add error information if this is a failure event + if (!success && error) { + span.setAttributes({ + 'pwa.error': error + }); + span.recordException(new Error(error)); + } + + span.end(); + + // Create a metric counter for package events + if (meter) { + try { + const packageCounter = meter.createCounter('pwa_package_events', { + description: 'Count of PWA package creation events' + }); + + packageCounter.add(1, { + success: success.toString(), + platform: 'meta-horizon' + }); + } catch (metricError) { + console.warn('Failed to record metric:', metricError); + } + } + + } catch (e) { + console.error('Error tracking event with OpenTelemetry:', e); + } +} + +export type AnalyticsInfo = { + url: string; + name: string; + packageId: string; + platformId: string | null; + platformIdVersion: string | null; + correlationId: string | null; + referrer: string | null; +}; diff --git a/apps/pwabuilder-meta-horizon/services/azureStorageBlobService.ts b/apps/pwabuilder-meta-horizon/services/azureStorageBlobService.ts new file mode 100644 index 000000000..8f453c66c --- /dev/null +++ b/apps/pwabuilder-meta-horizon/services/azureStorageBlobService.ts @@ -0,0 +1,155 @@ +import fs from 'fs-extra'; +import type { BlobServiceClient, ContainerClient } from "@azure/storage-blob"; + +/** + * A service for uploading and downloading files. In local development, this will be a service that uses the local file system. Otherwise, this will be a service that uses Azure Storage blob containers. + */ +export interface BlobStorage { + uploadFile(filePath: string, blobName: string): Promise; + downloadFileStream(blobName: string): Promise; +} + +/** + * Blob storage service that connects to PWABuilder's Azure Storage account for uploading and downloading files. + */ +export class AzureStorageBlobService implements BlobStorage { + private blobServiceClientTask: Promise | null = null; + private containerClientTask: Promise | null = null; + private readonly containerName = "meta-horizon-packages"; + private readonly azureStorageAccountName: string; + private readonly azureManagedIdentityAppId: string; + + constructor() { + this.azureStorageAccountName = process.env.AZURE_STORAGE_ACCOUNT_NAME || ""; + if (!this.azureStorageAccountName) { + throw new Error("AZURE_STORAGE_ACCOUNT_NAME environment variable is not set."); + } + + this.azureManagedIdentityAppId = process.env.AZURE_MANAGED_IDENTITY_APPLICATION_ID || ""; + if (!this.azureManagedIdentityAppId) { + throw new Error("AZURE_MANAGED_IDENTITY_APPLICATION_ID environment variable is not set."); + } + } + + /** + * Uploads a file to the meta-horizon-packages container in PWABuilder's Azure Storage account. + */ + async uploadFile(filePath: string, blobName: string): Promise { + try { + const containerClient = await this.initializeContainerClient(); + const blobSafeFileName = this.getBlobSafeFileName(blobName); + const blockBlobClient = containerClient.getBlockBlobClient(blobSafeFileName); + + // Note: our Azure Blob Storage account is configured to automatically delete these after several hours. + console.info(`Uploading file ${filePath} to blob ${blobSafeFileName}...`); + await blockBlobClient.uploadFile(filePath); + + console.info(`Successfully uploaded ${blobName} to Azure Blob Storage`); + return blockBlobClient.name; + } catch (error) { + console.error(`Error uploading file to Azure Blob Storage:`, error); + throw error; + } + } + + /** + * Downloads a blob from the meta-horizon-packages container as a stream + */ + async downloadFileStream(blobName: string): Promise { + try { + const containerClient = await this.initializeContainerClient(); + const blockBlobClient = containerClient.getBlockBlobClient(blobName); + + console.info(`Downloading blob ${blobName} as stream...`); + const downloadResponse = await blockBlobClient.download(); + + if (!downloadResponse.readableStreamBody) { + throw new Error(`Failed to download blob ${blobName}: no readable stream available`); + } + + console.info(`Successfully initiated stream download for ${blobName}`); + return downloadResponse.readableStreamBody; + } catch (error) { + console.error(`Error downloading file from Azure Blob Storage:`, error); + throw error; + } + } + + private getBlobSafeFileName(originalFileName: string): string { + return originalFileName + .replace(/\s+/g, '-') + .replace(/:/g, '-') + .replace(/[^a-zA-Z0-9-_]/g, ''); + } + + private initializeBlobServiceClient(): Promise { + if (!this.blobServiceClientTask) { + this.blobServiceClientTask = new Promise(async (resolve, reject) => { + try { + console.info("Initializing Azure services..."); + + console.info("Azure services initialized."); + + // Dynamic imports to prevent early loading of Azure packages + const { BlobServiceClient } = await import("@azure/storage-blob"); + const { DefaultAzureCredential } = await import("@azure/identity"); + + // Use user-assigned managed identity for authentication + const credential = new DefaultAzureCredential({ + managedIdentityClientId: this.azureManagedIdentityAppId + }); + const client = new BlobServiceClient( + `https://${this.azureStorageAccountName}.blob.core.windows.net`, + credential + ); + resolve(client); + } catch (error) { + reject(error); + } + }); + } + + return this.blobServiceClientTask; + } + + private initializeContainerClient(): Promise { + if (!this.containerClientTask) { + this.containerClientTask = this.initializeBlobServiceClient() + .then(blobService => blobService.getContainerClient(this.containerName)); + } + + return this.containerClientTask; + } +} + +/** + * Blob storage service that uses the local file system for uploading and downloading files. This is only used in local development. + */ +class LocalFileSystemBlobService implements BlobStorage { + private readonly blobNamesToFilePaths = new Map(); + + async uploadFile(filePath: string, blobName: string): Promise { + console.info(`Simulating upload of file ${filePath} as blob ${blobName}`); + this.blobNamesToFilePaths.set(blobName, filePath); + return new Promise((resolve) => setTimeout(() => resolve(blobName), 1000)); + } + + async downloadFileStream(blobName: string): Promise { + console.info(`Simulating download of blob ${blobName} as stream...`); + const filePath = this.blobNamesToFilePaths.get(blobName); + if (!filePath) { + throw new Error(`Blob ${blobName} not found in local storage simulation.`); + } + return Promise.resolve(fs.createReadStream(filePath)); + } +} + +const isLocalDev = process.env.NODE_ENV === "development" || process.env.NODE_ENV === undefined; +if (isLocalDev) { + console.info("Local development detected, using LocalFileSystemBlobService for blob storage."); +} else { + console.info(`${process.env.NODE_ENV} environment detected. Using AzureStorageBlobService for blob storage.`); +} +export const blobStorage = isLocalDev ? + new LocalFileSystemBlobService() as BlobStorage : + new AzureStorageBlobService() as BlobStorage; diff --git a/apps/pwabuilder-meta-horizon/services/bubbleWrapper.ts b/apps/pwabuilder-meta-horizon/services/bubbleWrapper.ts new file mode 100644 index 000000000..4a485d043 --- /dev/null +++ b/apps/pwabuilder-meta-horizon/services/bubbleWrapper.ts @@ -0,0 +1,417 @@ +import { + AndroidSdkTools, + Config, + DigitalAssetLinks, + GradleWrapper, + JdkHelper, + TwaGenerator, + TwaManifest, + JarSigner, + ConsoleLog, + SigningKeyInfo, +} from '@meta-quest/bubblewrap-core'; +import { ShortcutInfo } from '@meta-quest/bubblewrap-core/dist/lib/ShortcutInfo.js'; +import { findSuitableIcon } from '@meta-quest/bubblewrap-core/dist/lib/util.js'; +import { MetaHorizonPackageOptions } from '../models/metaHorizonPackageOptions.js'; +import fs from 'fs-extra'; +import { KeyTool, CreateKeyOptions } from '@meta-quest/bubblewrap-core/dist/lib/jdk/KeyTool.js'; +import { WebManifestShortcutJson } from '@meta-quest/bubblewrap-core/dist/lib/types/WebManifest.js'; +import { LocalKeyFileSigningOptions } from '../models/signingOptions.js'; +import { GeneratedAppPackage } from '../models/generatedAppPackage.js'; +import { TwaManifestJson } from '@meta-quest/bubblewrap-core/dist/lib/TwaManifest.js'; +import { fetchUtils } from '@meta-quest/bubblewrap-core'; +import { FetchEngine } from '@meta-quest/bubblewrap-core/dist/lib/FetchUtils.js'; +import generatePassword from 'password-generator'; +import { PackageCreationProgress } from "../models/packageCreationProgress.js"; +import EventEmitter from "events"; + +/* + * Wraps Meta Quest's bubblewrap to build a signed APK / AAB from a PWA, ready for the Meta Horizon Store. + * https://github.com/meta-quest/bubblewrap + */ +export class BubbleWrapper { + private javaConfig: Config; + private jdkHelper: JdkHelper; + private androidSdkTools: AndroidSdkTools; + private readonly eventEmitter = new EventEmitter(); + + /** + * @param packageSettings The settings for the Meta Horizon package generation. + * @param projectDirectory The directory where to generate the project files and signed APK. + * @param signingKeyInfo Information about the signing key. + */ + constructor( + private packageSettings: MetaHorizonPackageOptions, + private projectDirectory: string, + private signingKeyInfo: LocalKeyFileSigningOptions | null, + fetchEngine: FetchEngine + ) { + this.javaConfig = new Config( + process.env.JDK8PATH!, + process.env.ANDROIDTOOLSPATH! + ); + this.jdkHelper = new JdkHelper(process, this.javaConfig); + this.androidSdkTools = new AndroidSdkTools( + process, + this.javaConfig, + this.jdkHelper + ); + fetchUtils.setFetchEngine(fetchEngine); + } + + addEventListener(type: "progress", listener: (e: PackageCreationProgress) => void) { + this.eventEmitter.addListener(type, listener); + } + + removeEventListener(type: "progress", listener: (e: PackageCreationProgress) => void) { + this.eventEmitter.removeListener(type, listener); + } + + /** + * Generates app package from the PWA, suitable for the Meta Horizon Store. + */ + async generateAppPackage(): Promise { + this.dispatchProgressEvent('Creating Trusted Web Activity (TWA) project for Meta Horizon...'); + await this.generateTwaProject(); + + const apkPath = await this.buildApk(); + + // Do we have a signing key? + if (this.packageSettings.signingMode !== 'none' && this.signingKeyInfo) { + if (this.packageSettings.signingMode === "new") { + // temporary workaround for https://github.com/GoogleChromeLabs/bubblewrap/issues/693 + const passToUse = generatePassword(12, false); + + this.signingKeyInfo.keyPassword = passToUse; + this.signingKeyInfo.storePassword = passToUse; + } + + this.dispatchProgressEvent('Signing APK...'); + const signedApkPath = await this.signApk(apkPath, this.signingKeyInfo); + const assetLinksPath = await this.tryGenerateAssetLinks( + this.signingKeyInfo + ); + + this.dispatchProgressEvent('Building App Bundle...'); + const appBundlePath = await this.buildAppBundle(this.signingKeyInfo); + this.dispatchProgressEvent("App bundle built successfully."); + return { + projectDirectory: this.projectDirectory, + appBundleFilePath: appBundlePath, + apkFilePath: signedApkPath, + signingInfo: this.signingKeyInfo, + assetLinkFilePath: assetLinksPath, + }; + } + + // We generated an unsigned APK, so there will be no signing info, asset links, or app bundle. + return { + projectDirectory: this.projectDirectory, + apkFilePath: apkPath, + signingInfo: this.signingKeyInfo, + assetLinkFilePath: null, + appBundleFilePath: await this.buildAppBundle(), + }; + } + + private async buildAppBundle( + signingInfo?: LocalKeyFileSigningOptions + ): Promise { + this.dispatchProgressEvent('Generating app bundle'); + + const gradleWrapper = new GradleWrapper( + process, + this.androidSdkTools, + this.projectDirectory + ); + await gradleWrapper.bundleRelease(); + + const appBundleDir = 'app/build/outputs/bundle/release'; + const inputFile = `${this.projectDirectory}/${appBundleDir}/app-release.aab`; + + if (!signingInfo) { + return inputFile; + } + + const outputFile = `${this.projectDirectory}/${appBundleDir}/app-release-signed.aab`; + const jarSigner = new JarSigner(this.jdkHelper); + // JarSigner.sign invokes jarsigner via execFile (no shell), so args + // must be passed raw — see signApk for the same fix. + const jarSigningInfo: SigningKeyInfo = { + path: signingInfo.keyFilePath, + alias: signingInfo.alias, + }; + + try { + await jarSigner.sign( + jarSigningInfo, + signingInfo.storePassword, + signingInfo.keyPassword, + inputFile, + outputFile + ); + return outputFile; + } catch (signingError) { + const signingErrorStr = `${signingError}`; + if (signingErrorStr.includes("toDerInputStream rejects tag type 75")) { + this.dispatchProgressEvent("Error signing the app bundle due to what appears to be an invalid signing key file.", "error"); + } else { + this.dispatchProgressEvent("Error signing the app bundle.", "error"); + } + + console.error("Error signing the app bundle", signingError); + throw signingError; + } + } + + private async generateTwaProject(): Promise { + const twaGenerator = new TwaGenerator(); + const twaManifest = this.createTwaManifest(this.packageSettings); + try { + await twaGenerator.createTwaProject( + this.projectDirectory, + twaManifest, + new ConsoleLog() + ); + } catch (error) { + if (error instanceof Error && error.message.includes("Unexpected token '<'")) { + this.dispatchProgressEvent("It appears your web app manifest is broken. When PWABuilder's Meta Horizon platform went to fetch it, your web app returned HTML instead of JSON. Make sure your web manifest can be loaded directly. Try opening your web app in the browser, open F12 dev tools -> Application -> Manifest, and ensure your web manifest is loading properly.", "error"); + } else if (error instanceof Error && error.message.includes("Could not find MIME for Buffer")) { + this.dispatchProgressEvent("PWABuilder's Bubblewrap couldn't figure out the MIME type for one of the images in your web manifest. We recommend all your web manifest images should be PNG or JPG.", "error"); + } + + throw error; + } + return twaManifest; + } + + private async createSigningKey(signingInfo: LocalKeyFileSigningOptions) { + const keyTool = new KeyTool(this.jdkHelper); + const overwriteExisting = true; + if ( + !signingInfo.fullName || + !signingInfo.organization || + !signingInfo.organizationalUnit || + !signingInfo.countryCode + ) { + throw new Error( + `Missing required signing info. Full name: ${signingInfo.fullName}, Organization: ${signingInfo.organization}, Organizational Unit: ${signingInfo.organizationalUnit}, Country Code: ${signingInfo.countryCode}.` + ); + } + + const keyOptions: CreateKeyOptions = { + path: signingInfo.keyFilePath, + password: signingInfo.storePassword, + keypassword: signingInfo.keyPassword, + alias: signingInfo.alias, + fullName: signingInfo.fullName, + organization: signingInfo.organization, + organizationalUnit: signingInfo.organizationalUnit, + country: signingInfo.countryCode, + }; + + await keyTool.createSigningKey(keyOptions, overwriteExisting); + } + + private async buildApk(): Promise { + const gradleWrapper = new GradleWrapper( + process, + this.androidSdkTools, + this.projectDirectory + ); + this.dispatchProgressEvent("Building the app package with Gradle. This can take a few minutes..."); + await gradleWrapper.assembleRelease(); + return `${this.projectDirectory}/app/build/outputs/apk/release/app-release-unsigned.apk`; + } + + private async signApk( + apkFilePath: string, + signingInfo: LocalKeyFileSigningOptions + ): Promise { + if (this.packageSettings.signingMode === 'new') { + await this.createSigningKey(signingInfo); + } + + const outputFile = `${this.projectDirectory}/app-release-signed.apk`; + this.dispatchProgressEvent('Signing the app package...'); + // androidSdkTools.apksigner invokes apksigner via execFile (no shell), + // so the args must be passed raw — wrapping in literal double quotes + // bakes the quotes into the password and breaks keystore loading. + await this.androidSdkTools.apksigner( + signingInfo.keyFilePath, + signingInfo.storePassword, + signingInfo.alias, + signingInfo.keyPassword, + apkFilePath, + outputFile + ); + this.dispatchProgressEvent('App package signed successfully'); + return outputFile; + } + + async tryGenerateAssetLinks( + signingInfo: LocalKeyFileSigningOptions + ): Promise { + try { + const result = await this.generateAssetLinks(signingInfo); + return result; + } catch (error) { + this.dispatchProgressEvent(`Asset links couldn't be generated. Proceeding without asset links. Error: ${error}`); + return null; + } + } + + async generateAssetLinks( + signingInfo: LocalKeyFileSigningOptions + ): Promise { + this.dispatchProgressEvent('Generating asset links...'); + const keyTool = new KeyTool(this.jdkHelper); + const assetLinksFilePath = `${this.projectDirectory}/app/build/outputs/apk/release/assetlinks.json`; + const keyInfo = await keyTool.keyInfo({ + path: signingInfo.keyFilePath, + alias: signingInfo.alias, + keypassword: signingInfo.keyPassword, + password: signingInfo.storePassword, + }); + + const sha256Fingerprint = keyInfo.fingerprints.get('SHA256'); + if (!sha256Fingerprint) { + throw new Error("Couldn't find SHA256 fingerprint."); + } + + const assetLinks = DigitalAssetLinks.generateAssetLinks( + this.packageSettings.packageId, + sha256Fingerprint + ); + await fs.promises.writeFile(assetLinksFilePath, assetLinks); + this.dispatchProgressEvent(`Digital Asset Links file generated at ${assetLinksFilePath}`); + return assetLinksFilePath; + } + + private createTwaManifest(pwaSettings: MetaHorizonPackageOptions): TwaManifest { + // Bubblewrap expects a TwaManifest object. + // We create one using our package settings and signing key info. + + // Host without HTTPS: this is needed because the current version of Bubblewrap doesn't handle + // a host with protocol specified. Remove the protocol here. + let hostWithoutHttps = pwaSettings.host; + const httpsProtocol = 'https://'; + if (hostWithoutHttps.startsWith(httpsProtocol)) { + hostWithoutHttps = hostWithoutHttps.substring(httpsProtocol.length); + } + + // Trim any trailing slash from the host. + if (hostWithoutHttps.endsWith('/')) { + hostWithoutHttps = hostWithoutHttps.substring( + 0, + hostWithoutHttps.length - 1 + ); + } + + const signingKey = { + path: this.signingKeyInfo?.keyFilePath || '', + alias: this.signingKeyInfo?.alias || '', + }; + + // Alpha dependencies must be turned on if Play Billing or Horizon Billing is enabled. + const alphaDependencies = (pwaSettings.features?.playBilling?.enabled || + pwaSettings.features?.horizonBilling?.enabled) + ? { enabled: true } + : undefined; + + // Meta Horizon (Quest) requires API level 23+ at minimum. + const minSdkVersion = pwaSettings.minSdkVersion ?? 23; + + // In the @meta-quest/bubblewrap-core fork, `applicationId` on TwaManifest is NOT the Android + // applicationId (that is `packageId`). For Meta Quest, it's the OCULUS_APP_ID emitted into + // AndroidManifest as the `com.meta.horizon.platform.ovr.OCULUS_APP_ID` meta-data, used by + // the Meta Horizon Store (in-app purchase, platform SDK). It comes from the Meta Developer + // Dashboard. Bubblewrap's own `fromWebManifestJson` defaults this to '0'. + const metaHorizonAppId = pwaSettings.metaHorizonAppId || '0'; + + const manifestJson: TwaManifestJson = { + ...pwaSettings, + applicationId: metaHorizonAppId, + host: hostWithoutHttps, + shortcuts: this.createShortcuts( + pwaSettings.shortcuts, + pwaSettings.webManifestUrl + ), + signingKey: signingKey, + generatorApp: 'PWABuilder', + alphaDependencies: alphaDependencies, + isMetaQuest: true, + minSdkVersion, + }; + const twaManifest = new TwaManifest(manifestJson); + console.info('TWA manifest created', twaManifest); + return twaManifest; + } + + private createShortcuts( + shortcutsJson: WebManifestShortcutJson[] | null | undefined, + manifestUrl: string + ): ShortcutInfo[] { + if (!shortcutsJson) { + return []; + } + if (!manifestUrl) { + console.warn( + 'Skipping app shortcuts due to empty manifest URL', + manifestUrl + ); + return []; + } + + return shortcutsJson + .filter((s) => this.isValidShortcut(s)) + .map((s) => this.createShortcut(s, manifestUrl)) + .slice(0, 4); + } + + private createShortcut( + shortcut: WebManifestShortcutJson, + manifestUrl: string + ): ShortcutInfo { + const shortNameMaxSize = 12; + const name = shortcut.name || shortcut.short_name; + const shortName = + shortcut.short_name || shortcut.name!.substring(0, shortNameMaxSize); + const url = new URL(shortcut.url!, manifestUrl).toString(); + const suitableIcon = findSuitableIcon(shortcut.icons!, 'any'); + const iconUrl = new URL(suitableIcon!.src, manifestUrl).toString(); + return new ShortcutInfo(name!, shortName!, url, iconUrl); + } + + private isValidShortcut( + shortcut: WebManifestShortcutJson | null | undefined + ): boolean { + if (!shortcut) { + this.dispatchProgressEvent(`Shortcut is invalid due to being null or undefined ${shortcut}`); + return false; + } + if (!shortcut.icons) { + this.dispatchProgressEvent(`Shortcut is invalid due to not having any icons specified ${shortcut}`); + return false; + } + if (!shortcut.url) { + this.dispatchProgressEvent(`Shortcut is invalid due to not having a URL ${shortcut}`); + return false; + } + if (!shortcut.name && !shortcut.short_name) { + this.dispatchProgressEvent(`Shortcut is invalid due to having neither a name nor short_name ${shortcut}`); + return false; + } + if (!findSuitableIcon(shortcut.icons, 'any')) { + this.dispatchProgressEvent(`Shortcut is invalid due to not finding a suitable icon ${shortcut.icons}`); + return false; + } + + return true; + } + + private dispatchProgressEvent(message: string, level: "info" | "warn" | "error" = "info"): void { + const ev: PackageCreationProgress = { message, level }; + this.eventEmitter.emit("progress", ev); + } +} diff --git a/apps/pwabuilder-meta-horizon/services/packageCreator.ts b/apps/pwabuilder-meta-horizon/services/packageCreator.ts new file mode 100644 index 000000000..c6c9a7d36 --- /dev/null +++ b/apps/pwabuilder-meta-horizon/services/packageCreator.ts @@ -0,0 +1,432 @@ +import { MetaHorizonPackageOptions } from "../models/metaHorizonPackageOptions.js"; +import { GeneratedAppPackage } from "../models/generatedAppPackage.js"; +import tmp from "tmp"; +import { LocalKeyFileSigningOptions } from "../models/signingOptions.js"; +import { BubbleWrapper } from './bubbleWrapper.js'; +import { join } from "path"; +import fs from 'fs-extra'; +import { base64ToBuffer } from "../utils/base64ToBuffer.js"; +import archiver from 'archiver'; +import { deleteAsync } from 'del'; +import { AnalyticsInfo, trackEvent } from './analytics.js'; +import { msToFriendly } from "../utils/msToFriendly.js"; +import { PackageCreationProgress } from "../models/packageCreationProgress.js"; +import EventEmitter from "events"; +import { errorToString } from "../utils/errorToString.js"; + +/** + * Generates an app package ready for upload to the Meta Horizon Store. + */ +export class PackageCreator { + private readonly tempFileRemovalTimeoutMs = 1000 * 60 * 10; // 10 minutes + private readonly eventEmitter = new EventEmitter(); + + /** + * Creates a new app package ready for upload to the Meta Horizon Store. + */ + public async create(packageOptions: MetaHorizonPackageOptions): Promise { + const startTime = Date.now(); + try { + const appPackage = await this.createAppPackage(packageOptions); + this.dispatchProgressEvent(`Successfully created app package for ${packageOptions.pwaUrl}`); + this.analyticsTrackPackageCreationSuccess(packageOptions); + return appPackage; + } catch (err) { + const errorStr = errorToString(err); + this.dispatchProgressEvent(`Error creating app package: ${errorStr}`, "error"); + this.analyticsTrackPackageCreationFailure(packageOptions, errorStr); + throw err; + } finally { + const durationMs = Date.now() - startTime; + this.dispatchProgressEvent(`Process completed in ${msToFriendly(durationMs)}`); + } + } + + /** + * Creates a Meta Horizon package and zips up the results. + */ + public async createZip(packageOptions: MetaHorizonPackageOptions): Promise { + const appPackage = await this.create(packageOptions); + try { + const zipFilePath = await this.zipAppPackage(appPackage, packageOptions); + return zipFilePath; + } catch (err) { + const errorStr = errorToString(err); + this.dispatchProgressEvent(`Error zipping app package: ${errorStr}`, "error"); + this.analyticsTrackPackageCreationFailure(packageOptions, errorStr); + throw err; + } + } + + addEventListener(type: "progress", listener: (e: PackageCreationProgress) => void) { + this.eventEmitter.addListener(type, listener); + } + + removeEventListener(type: "progress", listener: (e: PackageCreationProgress) => void) { + this.eventEmitter.removeListener(type, listener); + } + + private async createAppPackage(options: MetaHorizonPackageOptions): Promise { + this.dispatchProgressEvent(`Generating Meta Horizon app package for ${options.pwaUrl}`); + let projectDir: tmp.DirResult | null = null; + try { + this.dispatchProgressEvent(`Creating temp directory...`); + projectDir = tmp.dirSync({ prefix: 'pwabuilder-metahorizon-' }); + const projectDirPath = projectDir.name; + + this.dispatchProgressEvent(`Creating signing information...`); + const signing = await this.createLocalSigningKeyInfo(options, projectDirPath); + + return await this.createAppPackageWith403Fallback( + options, + projectDirPath, + signing + ); + } finally { + this.scheduleTmpDirectoryCleanup(projectDir?.name); + } + } + + /** + * Returns true if the given URL is considered safe to fetch from this service. + */ + private isSafeUrlForFetch(url: string): boolean { + if (!url) { + return false; + } + let parsed: URL; + try { + parsed = new URL(url); + } catch { + return false; + } + + const protocol = parsed.protocol.toLowerCase(); + if (protocol !== 'http:' && protocol !== 'https:') { + return false; + } + + const hostname = parsed.hostname.toLowerCase(); + + if ( + hostname === 'localhost' || + hostname === '127.0.0.1' || + hostname === '::1' || + hostname.endsWith('.localhost') || + hostname.endsWith('.local') || + hostname.endsWith('.localdomain') || + hostname.endsWith('.internal') + ) { + return false; + } + + return true; + } + + private async createAppPackageWith403Fallback( + options: MetaHorizonPackageOptions, + projectDirPath: string, + signing: LocalKeyFileSigningOptions | null + ): Promise { + // If we get a 403 error or timeout, retry using PWABuilder's URL proxy service. + const http1Fetch = 'node-fetch'; + const http2Fetch = 'fetch-h2'; + try { + const bubbleWrapper = new BubbleWrapper( + options, + projectDirPath, + signing, + http1Fetch + ); + this.dispatchProgressEvent('Using Bubblewrap to generate app package...'); + bubbleWrapper.addEventListener("progress", e => this.bubblewrapProgress(e)) + return await bubbleWrapper.generateAppPackage(); + } catch (error) { + const errorMessage = (error as Error)?.message || `${error}`; + this.dispatchProgressEvent("Unable to generate app package due to error. Checking if error is 403 Forbidden or timeout. " + errorMessage, "warn"); + const is403Error = + (error as any)?.status === 403 || + (error as any)?.response?.status === 403 || + errorMessage.includes('403') || + errorMessage.includes('ECONNREFUSED') || + errorMessage.includes('ENOTFOUND'); + const isTimeout = errorMessage.includes('ETIMEDOUT') || errorMessage.includes('ESOCKETTIMEDOUT'); + const isRunDotAppManifestError = options.pwaUrl.indexOf("run.app/") && errorMessage.includes("Unexpected token"); + if (is403Error || isTimeout || isRunDotAppManifestError) { + const optionsWithSafeUrl = this.getMetaHorizonOptionsWithProxiedUrls(options); + const isCloudflare = await this.TryCheckCloudflare(options.iconUrl); + if (isCloudflare) { + this.dispatchProgressEvent("Cloudflare is blocking PWABuilder from accessing your app's images. If the problem persists, please temporarily disable Cloudflare's \"Bot fight mode\" while you're packaging with PWABuilder. For more help, see https://docs.pwabuilder.com/#/builder/faq?id=error-403-forbidden-during-analysis-or-packaging", "warn"); + } else { + this.dispatchProgressEvent("Your web app is blocking PWABuilder from accessing your app's images, serving 403 Forbidden errors to PWABuilder. If the problem persists, please temporarily disable your firewall, CDN, or Cloudflare while packaging with PWABuilder. For more help, see https://docs.pwabuilder.com/#/builder/faq?id=error-403-forbidden-during-analysis-or-packaging", "warn"); + } + const bubbleWrapper = new BubbleWrapper( + optionsWithSafeUrl, + projectDirPath, + signing, + http2Fetch + ); + bubbleWrapper.addEventListener("progress", e => this.bubblewrapProgress(e)); + return await bubbleWrapper.generateAppPackage(); + } + + this.dispatchProgressEvent(`Bubblewrap failed to generate an app package due to an error. ${error}`, "error"); + throw error; + } + } + + private async createLocalSigningKeyInfo(packageSettings: MetaHorizonPackageOptions, projectDir: string): Promise { + if (packageSettings.signingMode === 'none') { + return null; + } + + const keyFilePath = join(projectDir, 'signingKey.keystore'); + if (packageSettings.signingMode === 'mine') { + if (!packageSettings.signing?.file) { + throw new Error("Signing mode is 'mine', but no signing key file was supplied."); + } + + const fileBuffer = base64ToBuffer(packageSettings.signing.file); + await fs.writeFile(keyFilePath, new Uint8Array(fileBuffer)); + } + + if (!packageSettings.signing) { + throw new Error( + `Signing mode was set to ${packageSettings.signingMode}, but no signing information was supplied.` + ); + } + + return { + keyFilePath: keyFilePath, + ...packageSettings.signing, + }; + } + + private TryCheckCloudflare(iconUrl: string): Promise { + return new Promise(async (resolve) => { + try { + if (!this.isSafeUrlForFetch(iconUrl)) { + resolve(false); + return; + } + + const response = await fetch(iconUrl, { method: 'GET' }); + const serverHeader = response.headers.get('Server') || ''; + resolve(!!serverHeader && serverHeader.includes('cloudflare')); + } catch (error) { + resolve(false); + } + }); + } + + private scheduleTmpFileCleanup(file: string | null): void { + if (file) { + console.info('Scheduled cleanup for tmp file', file); + const delFile = function () { + const filePath = file.replace(/\\/g, '/'); + deleteAsync([filePath], { force: true }) + .then((deletedPaths: string[]) => + console.info('Cleaned up tmp file', deletedPaths) + ) + .catch((err: any) => + console.warn( + 'Unable to cleanup tmp file. It will be cleaned up on process exit', + err, + filePath + ) + ); + }; + setTimeout(() => delFile(), this.tempFileRemovalTimeoutMs); + } + } + + private scheduleTmpDirectoryCleanup(dir?: string | null): void { + if (dir) { + const dirToDelete = dir.replace(/\\/g, '/'); + const dirPatternToDelete = dirToDelete + '/**'; + console.info('Scheduled cleanup for tmp directory', dirPatternToDelete); + const delDir = function () { + deleteAsync([dirPatternToDelete], { force: true }) + .then((deletedPaths: string[]) => + console.info( + 'Cleaned up tmp directory', + dirPatternToDelete, + deletedPaths?.length, + 'subdirectories and files were deleted' + ) + ) + .catch((err: any) => + console.warn( + 'Unable to cleanup tmp directory. It will be cleaned up on process exit', + err + ) + ); + }; + setTimeout(() => delDir(), this.tempFileRemovalTimeoutMs); + } + } + + private getMetaHorizonOptionsWithProxiedUrls(options: MetaHorizonPackageOptions): MetaHorizonPackageOptions { + const absoluteUrlProps: Array = [ + 'maskableIconUrl', + 'monochromeIconUrl', + 'iconUrl', + 'webManifestUrl', + ]; + const newOptions: MetaHorizonPackageOptions = { ...options }; + for (let prop of absoluteUrlProps) { + const url = newOptions[prop]; + if (url && typeof url === 'string') { + const absoluteUrl = new URL(url, options.webManifestUrl); + const isManifestUrl = prop === 'webManifestUrl'; + if (isManifestUrl) { + (newOptions as any)[prop] = `https://pwabuilder.com/api/manifests/getFromCacheOrProxy?manifestUrl=${encodeURIComponent(absoluteUrl.toString())}`; + this.dispatchProgressEvent(`Updated manifest URL to use manifest proxy. Old value ${options.webManifestUrl}, new value ${(newOptions)[prop]}`); + } else { + (newOptions as any)[prop] = PackageCreator.getImageProxyUrl(absoluteUrl, options.analysisId); + this.dispatchProgressEvent(`Updated ${prop} to use image proxy. Old value ${options[prop]}, new value ${(newOptions)[prop]}`); + } + } + } + + if (newOptions.shortcuts && Array.isArray(newOptions.shortcuts)) { + newOptions.shortcuts.forEach(shortcut => { + (shortcut.icons || []) + .filter(icon => !!icon.src) + .forEach(icon => { + const oldValue = icon.src; + icon.src = PackageCreator.getImageProxyUrl(new URL(icon.src, options.webManifestUrl), options.analysisId); + this.dispatchProgressEvent(`Updated shortcut icon to use image proxy. Old value ${oldValue}, new value ${icon.src}`); + }); + }); + } + + return newOptions; + } + + private static getImageProxyUrl(imageUrl: URL, analysisId: string | null): string { + return `https://pwabuilder.com/api/images/getSafeImageForAnalysis?imageUrl=${encodeURIComponent(imageUrl.toString())}&analysisId=${encodeURIComponent(analysisId || "")}`; + } + + /*** + * Creates a zip file containing the app package and associated artifacts. + */ + async zipAppPackage(appPackage: GeneratedAppPackage, packageOptions: MetaHorizonPackageOptions): Promise { + this.dispatchProgressEvent("Zipping app package..."); + const apkName = `${packageOptions.name}${packageOptions.signingMode === 'none' ? '-unsigned' : ''}.apk`; + let tmpZipFile: string | null = null; + + return new Promise((resolve, reject) => { + try { + const archive = archiver('zip', { + zlib: { level: 5 }, + }); + + archive.on('warning', function (zipWarning: any) { + console.warn('Warning during zip creation', zipWarning); + }); + archive.on('error', function (zipError: any) { + console.error('Error during zip creation', zipError); + reject(zipError); + }); + + tmpZipFile = tmp.tmpNameSync({ + prefix: 'pwabuilder-metahorizon-', + postfix: '.zip', + }); + const output = fs.createWriteStream(tmpZipFile); + output.on('close', () => { + if (tmpZipFile) { + this.dispatchProgressEvent("App package zipped successfully."); + resolve(tmpZipFile); + } else { + this.dispatchProgressEvent("An error occurred while creating the zip file.", "error"); + reject('No zip file was created'); + } + }); + + archive.pipe(output); + + const isSigned = !!appPackage.signingInfo; + archive.file(appPackage.apkFilePath, { name: apkName }); + archive.file( + isSigned ? './static/next-steps.html' : './static/next-steps-unsigned.html', + { name: 'Readme.html' } + ); + + if (appPackage.signingInfo && appPackage.signingInfo.keyFilePath) { + archive.file(appPackage.signingInfo.keyFilePath, { + name: 'signing.keystore', + }); + const readmeContents = [ + "Keep this file and signing.keystore in a safe place. You'll need these files if you want to upload future versions of your PWA to the Meta Horizon Store.\r\n", + 'Key store file: signing.keystore', + `Key store password: ${appPackage.signingInfo.storePassword}`, + `Key alias: ${appPackage.signingInfo.alias}`, + `Key password: ${appPackage.signingInfo.keyPassword}`, + `Signer's full name: ${appPackage.signingInfo.fullName}`, + `Signer's organization: ${appPackage.signingInfo.organization}`, + `Signer's organizational unit: ${appPackage.signingInfo.organizationalUnit}`, + `Signer's country code: ${appPackage.signingInfo.countryCode}`, + ]; + archive.append(readmeContents.join('\r\n'), { + name: 'signing-key-info.txt', + }); + + if (appPackage.assetLinkFilePath) { + archive.file(appPackage.assetLinkFilePath, { + name: 'assetlinks.json', + }); + } + } + + if (appPackage.appBundleFilePath) { + archive.file(appPackage.appBundleFilePath, { + name: `${packageOptions.name}${packageOptions.signingMode === 'none' ? '-unsigned' : ''}.aab`, + }); + } + + if (packageOptions.includeSourceCode) { + archive.directory(appPackage.projectDirectory, 'source'); + } + + archive.finalize(); + } catch (err) { + reject(err); + } finally { + this.scheduleTmpFileCleanup(tmpZipFile); + } + }); + } + + private analyticsTrackPackageCreationSuccess(options: MetaHorizonPackageOptions) { + const analyticsInfo = this.metaHorizonOptionsToAnalyticsInfo(options); + trackEvent(analyticsInfo, null, true); + } + + private analyticsTrackPackageCreationFailure(options: MetaHorizonPackageOptions, error: string) { + const analyticsInfo = this.metaHorizonOptionsToAnalyticsInfo(options); + trackEvent(analyticsInfo, error, false); + } + + private metaHorizonOptionsToAnalyticsInfo(options: MetaHorizonPackageOptions): AnalyticsInfo { + return { + url: options.pwaUrl || options.host || '', + packageId: options.packageId || '', + name: options.name || '', + platformId: options.analyticsInfo?.platformId || null, + platformIdVersion: options.analyticsInfo?.platformIdVersion || null, + correlationId: options.analyticsInfo?.correlationId || null, + referrer: options.analyticsInfo?.referrer || null, + }; + } + + private bubblewrapProgress(e: PackageCreationProgress): void { + this.eventEmitter.emit("progress", e); + } + + private dispatchProgressEvent(message: string, level: "info" | "warn" | "error" = "info"): void { + const event: PackageCreationProgress = { message, level }; + this.eventEmitter.emit("progress", event); + } +} diff --git a/apps/pwabuilder-meta-horizon/services/packageJobProcessor.ts b/apps/pwabuilder-meta-horizon/services/packageJobProcessor.ts new file mode 100644 index 000000000..e6dd586ee --- /dev/null +++ b/apps/pwabuilder-meta-horizon/services/packageJobProcessor.ts @@ -0,0 +1,135 @@ +import { MetaHorizonPackageJob } from "../models/metaHorizonPackageJob.js"; +import { PackageCreator } from "./packageCreator.js"; +import { PackageCreationProgress } from "../models/packageCreationProgress.js"; +import { PackageJobLogger } from "../models/packageJobLogger.js"; +import { blobStorage } from "./azureStorageBlobService.js"; +import { database } from "./redisService.js"; +import { packageJobQueue } from "./packageJobQueue.js"; + +/** + * Inspects Redis to see if there are any pending Meta Horizon packaging jobs. If so, it dequeues the job and begins processing it. + */ +export class PackageJobProcessor { + + private readonly jobCheckIntervalMs = 2000; + private readonly maxRetryCount = 2; + + /** + * Begins the background job processor that periodically checks for Meta Horizon packaging jobs and processes them. + */ + start(): void { + database.ready.then(() => { + console.info('Database ready, starting job processor in 5 seconds to allow for complete initialization...'); + setTimeout(() => this.runJobs(), 5000); + }).catch((error) => { + console.error('Failed to start job processor due to database initialization error:', error); + }); + } + + private async runJobs(): Promise { + let job: MetaHorizonPackageJob | null = null; + let jobLogger: PackageJobLogger | null = null; + let hadTimeout = false; + + try { + const isHealthy = await database.healthCheck(); + if (!isHealthy) { + console.warn("Redis health check failed, skipping job dequeue cycle"); + hadTimeout = true; + return; + } + + job = await packageJobQueue.dequeue(); + if (job) { + jobLogger = new PackageJobLogger(job); + await this.processJob(job, jobLogger); + } + } catch (jobError) { + const error = jobError as Error; + const isRedisError = error.message && ( + error.message.includes('Command timed out') || + error.message.includes('Connection is closed') || + error.message.includes('Redis not ready') || + error.message.includes('already connecting/connected') + ); + + if (isRedisError) { + hadTimeout = true; + console.warn("Redis error during job dequeue - will retry on next cycle", error.message); + } else { + console.error("Error processing Meta Horizon packaging job", jobError); + if (job) { + this.retryJobOrMarkAsFailed(job, jobError, jobLogger || new PackageJobLogger(job)); + } + } + } finally { + const delay = hadTimeout ? 15000 : this.jobCheckIntervalMs; + setTimeout(() => this.runJobs(), delay); + } + } + + private async processJob(job: MetaHorizonPackageJob, logger: PackageJobLogger): Promise { + const packageCreator = new PackageCreator(); + const progressHandler = (e: PackageCreationProgress) => this.jobProgressed(e, job, logger); + packageCreator.addEventListener("progress", progressHandler); + try { + job.status = "InProgress"; + await database.save(job.id, job); + + const zipFilePath = await packageCreator.createZip(job.packageOptions); + this.jobProgressed({ level: "info", message: "Successfully generated Meta Horizon package. Saving zip file..." }, job, logger); + await this.jobCompleted(job, zipFilePath, logger); + + } catch (err) { + logger.error("Error during package creation", err); + throw err; + } finally { + packageCreator.removeEventListener("progress", progressHandler); + } + } + + private async retryJobOrMarkAsFailed(job: MetaHorizonPackageJob, jobError: any, logger: PackageJobLogger): Promise { + if (job.retryCount < this.maxRetryCount) { + job.retryCount++; + logger.info("Retrying job", { attempt: job.retryCount + 1, maxAttempts: this.maxRetryCount + 1 }); + await database.save(job.id, job); + await packageJobQueue.requeue(job); + } else { + logger.error(`Job failed after ${this.maxRetryCount + 1} attempts.`, jobError); + job.status = "Failed"; + await database.save(job.id, job); + } + } + + async jobProgressed(e: PackageCreationProgress, job: MetaHorizonPackageJob, logger: PackageJobLogger) { + logger.logProgress(e); + + try { + await database.save(job.id, job); + } catch (statusSaveError) { + logger.error("Failed to save job progress to Redis", statusSaveError); + } + } + + async jobCompleted(job: MetaHorizonPackageJob, zipFilePath: string, jobLogger: PackageJobLogger) { + const fileName = job.id.replaceAll("metahorizonpackagejob", ""); + let blobFileName: string; + try { + blobFileName = await blobStorage.uploadFile(zipFilePath, `${fileName}.zip`); + } catch (uploadError) { + jobLogger.error("Error uploading package zip file to Azure Blob Storage.", uploadError); + throw uploadError; + } + + try { + job.status = "Completed"; + job.uploadedBlobFileName = blobFileName; + jobLogger.info("Successfully uploaded package zip file", blobFileName); + await database.save(job.id, job); + await packageJobQueue.recordProcessedJob(job.id); + } catch (completionError) { + jobLogger.error("Error marking job as completed.", completionError); + throw completionError; + } + } +} diff --git a/apps/pwabuilder-meta-horizon/services/packageJobQueue.ts b/apps/pwabuilder-meta-horizon/services/packageJobQueue.ts new file mode 100644 index 000000000..11c755836 --- /dev/null +++ b/apps/pwabuilder-meta-horizon/services/packageJobQueue.ts @@ -0,0 +1,129 @@ +import { MetaHorizonPackageOptions } from "../models/metaHorizonPackageOptions.js"; +import { MetaHorizonPackageJob } from "../models/metaHorizonPackageJob.js"; +import { createHash } from "../utils/hashCode.js"; +import { database } from "./redisService.js"; + +/** + * A queue containing Meta Horizon packaging jobs to be processed. Implemented as a Redis list. + */ +export class PackageJobQueue { + private readonly oneHourInMs = 60 * 60 * 1000; + private processedJobTimestamps: number[] = []; + private readonly startDate = new Date(); + + private get jobQueueKey(): string { + // Use a different queue key in production vs non-production so that staging and dev doesn't process production jobs. + return process.env.NODE_ENV === "production" ? "metahorizonpackagejobs-prod" : "metahorizonpackagejobs-nonprod"; + } + + /** + * Enqueues a new Meta Horizon packaging job. + */ + public async enqueue(packageArgs: MetaHorizonPackageOptions): Promise { + const job = this.createJobFromPackageArgs(packageArgs); + try { + await database.save(job.id, job); + + const queueLength = await database.enqueue(this.jobQueueKey, job); + console.info(`Enqueued new Meta Horizon packaging job ${job.id} to ${this.jobQueueKey}. ${queueLength} ${queueLength === 1 ? "job" : "jobs"} are now in the queue.`); + + return job.id; + } catch (enqueueError) { + console.error("Error enqueueing Meta Horizon packaging job", enqueueError); + throw enqueueError; + } + } + + /** + * Dequeues the next Meta Horizon packaging job from the queue. + */ + public async dequeue(): Promise { + try { + const jobData = await database.dequeue(this.jobQueueKey); + if (!jobData) { + return null; + } + + const queueLength = await database.queueLength(this.jobQueueKey); + console.info(`Dequeued Meta Horizon packaging job ${jobData.id} from ${this.jobQueueKey}. ${queueLength} jobs remaining in the queue.`); + + return jobData; + } catch (dequeueError) { + console.error("Error dequeuing Meta Horizon packaging job", dequeueError); + throw dequeueError; + } + } + + /** + * Re-enqueues an existing job to be processed later. + */ + public async requeue(job: MetaHorizonPackageJob): Promise { + try { + await database.enqueue(this.jobQueueKey, job); + } catch (enqueueError) { + console.error("Error enqueueing Meta Horizon packaging job", enqueueError); + throw enqueueError; + } + } + + /** + * Gets the current length of the package job queue. + */ + public async getQueueLength(): Promise { + try { + return await database.queueLength(this.jobQueueKey); + } catch (error) { + console.error("Error getting package job queue length", error); + throw error; + } + } + + /** + * Records that a package job finished processing. + */ + public async recordProcessedJob(_jobId: string): Promise { + const now = Date.now(); + this.processedJobTimestamps.push(now); + this.pruneProcessedJobTimestamps(now); + } + + /** + * Gets the count of package jobs processed in the last hour. + */ + public async getProcessedCountLastHour(): Promise { + const now = Date.now(); + const oneHourAgo = now - this.oneHourInMs; + this.pruneProcessedJobTimestamps(now); + + return this.processedJobTimestamps.filter(timestamp => timestamp >= oneHourAgo).length; + } + + public getStartDate(): Date { + return this.startDate; + } + + private pruneProcessedJobTimestamps(currentTimeMs: number): void { + const oneHourAgo = currentTimeMs - this.oneHourInMs; + this.processedJobTimestamps = this.processedJobTimestamps.filter(timestamp => timestamp >= oneHourAgo); + } + + private createJobFromPackageArgs(packageArgs: MetaHorizonPackageOptions): MetaHorizonPackageJob { + const hash = createHash(packageArgs).toString() + createHash(Date.now()); + const pwaUri = new URL(packageArgs.pwaUrl); + const id = `metahorizonpackagejob:${pwaUri.host}:${hash.slice(-6)}`; + return { + id: id, + pwaUrl: packageArgs.pwaUrl, + analysisId: null, + errors: [], + logs: [], + status: "Queued", + retryCount: 0, + packageOptions: packageArgs, + uploadedBlobFileName: null, + createdAt: new Date().toISOString() + }; + } +} + +export const packageJobQueue = new PackageJobQueue(); diff --git a/apps/pwabuilder-meta-horizon/services/redisService.ts b/apps/pwabuilder-meta-horizon/services/redisService.ts new file mode 100644 index 000000000..622dd2edc --- /dev/null +++ b/apps/pwabuilder-meta-horizon/services/redisService.ts @@ -0,0 +1,277 @@ +import { createClient, RedisClientType } from "@redis/client"; +import { DefaultAzureCredential } from "@azure/identity"; +import { EntraIdCredentialsProviderFactory, REDIS_SCOPE_DEFAULT } from "@redis/entraid"; + +/** + * A service for interacting with a Redis database. In local development, this will be implemented as an in-memory database. For non-local environments, it will be implemented as a Redis database. + */ +export interface RedisDatabaseService { + ready: Promise; + getJson(key: string): Promise; + save(key: string, value: T): Promise; + dequeue(key: string): Promise; + enqueue(key: string, value: T): Promise; + queueLength(key: string): Promise; + healthCheck(): Promise; +} + +/** + * Database service that connects to PWABuilder's Redis instance in Azure. + */ +export class RedisService implements RedisDatabaseService { + public readonly ready: Promise; + private readonly redis: RedisClientType; + private readonly redisHost: string; + + constructor() { + this.redisHost = process.env.REDIS_HOST || ""; + if (!this.redisHost) { + throw new Error("REDIS_HOST environment variable is not set."); + } + + this.redis = this.createRedisConnection(); + this.ready = this.initializeConnection(); + } + + private createRedisConnection(): RedisClientType { + // Create credentials provider for authentication + const credential = new DefaultAzureCredential(); + const credentialsProvider = EntraIdCredentialsProviderFactory.createForDefaultAzureCredential({ + credential, + scopes: REDIS_SCOPE_DEFAULT, + tokenManagerConfig: { + expirationRefreshRatio: 0.8 // Refresh token after 80% of its lifetime + } + }); + + return createClient({ + socket: { + host: this.redisHost, + port: 6380, + tls: true, + connectTimeout: 30000, // 30 seconds to establish connection (Azure cold start) + reconnectStrategy: (retries) => { + const delay = Math.min(retries * 100, 3000); + console.info(`Reconnecting to Redis, attempt ${retries}, waiting ${delay}ms`); + return delay; + } + }, + credentialsProvider, + commandsQueueMaxLength: 1000, + pingInterval: 30000 // Keep-alive ping every 30 seconds + }); + } + + private async initializeConnection(): Promise { + try { + this.redis.on("error", (err: Error) => { + console.error("Redis connection error:", err); + }); + + this.redis.on("reconnecting", () => { + console.info("Redis reconnecting..."); + }); + + await this.redis.connect(); + console.info("Redis connected and authenticated with managed identity"); + } catch (error) { + console.error("Failed to initialize Redis connection:", error); + throw error; + } + } + + /** + * Retrieves a JSON object from Redis. + */ + async getJson(key: string): Promise { + try { + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error(`Redis get timeout after 15 seconds for key: ${key}`)), 15000); + }); + + const json = await Promise.race([ + this.redis.get(key), + timeoutPromise + ]); + + if (!json) { + return null; + } + return JSON.parse(json) as T; + } catch (error) { + console.error(`Error in getJson for key ${key}:`, error); + throw error; + } + } + + /** + * Stores the given object as a JSON string in Redis. + */ + async save(key: string, value: T): Promise { + try { + const expirationSeconds = 90 * 24 * 60 * 60; // 90 days in seconds + + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error(`Redis set timeout after 15 seconds for key: ${key}`)), 15000); + }); + + await Promise.race([ + this.redis.set(key, JSON.stringify(value), { + EX: expirationSeconds + }), + timeoutPromise + ]); + + console.info("Saved object to redis", key); + } catch (error) { + console.error(`Error saving JSON to Redis with key ${key}:`, error); + throw error; + } + } + + /** + * Finds a list with the given key and removes and returns the first item from it. + */ + async dequeue(key: string): Promise { + try { + const startTime = Date.now(); + + if (!this.redis.isReady) { + console.warn('Redis not ready, skipping dequeue for key:', key); + return null; + } + + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error(`Redis lpop timeout after 10 seconds for key: ${key}`)), 10000); + }); + + const json = await Promise.race([ + this.redis.lPop(key), + timeoutPromise + ]); + + const duration = Date.now() - startTime; + + if (duration > 2000) { + console.warn(`Slow Redis lpop operation: ${duration}ms for key: ${key} `); + } + + if (!json) { + return null; + } + + return JSON.parse(json) as T; + } catch (error) { + console.error(`Error in dequeue for key ${key}: `, error); + + try { + console.error('Redis ready status after dequeue failure:', this.redis.isReady); + } catch (diagError) { + console.error('Failed to get Redis status:', diagError); + } + + throw error; + } + } + + /** + * Pushes the given value onto the end of the list with the given key. + */ + async enqueue(key: string, value: T): Promise { + let queueLength = await this.redis.lLen(key); + if (queueLength >= 50) { + throw new Error("Cannot enqueue Meta Horizon package because the queue is too long. Current queue length: " + queueLength); + } + + queueLength = await this.redis.rPush(key, JSON.stringify(value)); + console.info(`Added ${key} to the queue ${key}. Queue length is now ${queueLength} `); + + return queueLength; + } + + /** + * Gets the length of the queue with the given key. + */ + queueLength(key: string): Promise { + return this.redis.lLen(key); + } + + /** + * Checks if Redis connection is healthy + */ + async healthCheck(): Promise { + try { + if (!this.redis.isReady) { + return false; + } + + const pingPromise = this.redis.ping(); + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error('Health check timeout')), 3000); + }); + + await Promise.race([pingPromise, timeoutPromise]); + return true; + } catch (error) { + console.warn('Redis health check failed:', error); + return false; + } + } +} + +class InMemoryDatabaseService implements RedisDatabaseService { + private readonly store: Map = new Map(); + private readonly queues: Map = new Map(); + public readonly ready = Promise.resolve(); + + getJson(key: string): Promise { + const jsonOrNull = this.store.get(key) || null; + if (!jsonOrNull) { + return Promise.resolve(null); + } + + return Promise.resolve(JSON.parse(jsonOrNull) as T); + } + + save(key: string, value: T): Promise { + console.info("Saving object to in-memory storage", key); + this.store.set(key, JSON.stringify(value)); + return Promise.resolve(); + } + + dequeue(key: string): Promise { + const list = this.queues.get(key) || []; + const firstItemInList = list.shift(); + if (!firstItemInList) { + return Promise.resolve(null); + } + + return Promise.resolve(JSON.parse(firstItemInList) as T); + } + + enqueue(key: string, value: T): Promise { + const list = this.queues.get(key) || []; + list.push(JSON.stringify(value)); + this.queues.set(key, list); + return Promise.resolve(list.length); + } + + queueLength(key: string): Promise { + const list = this.queues.get(key) || []; + return Promise.resolve(list.length); + } + + healthCheck(): Promise { + return Promise.resolve(true); + } +} + +const isLocalDev = process.env.NODE_ENV === "development" || process.env.NODE_ENV === undefined; +if (isLocalDev) { + console.info("Local development detected, using in-memory database."); +} else { + console.info(`${process.env.NODE_ENV} environment detected.Using Redis for database service.`); +} +export const database = isLocalDev ? + new InMemoryDatabaseService() as RedisDatabaseService : + new RedisService() as RedisDatabaseService; diff --git a/apps/pwabuilder-meta-horizon/static/index.html b/apps/pwabuilder-meta-horizon/static/index.html new file mode 100644 index 000000000..7b59a198d --- /dev/null +++ b/apps/pwabuilder-meta-horizon/static/index.html @@ -0,0 +1,70 @@ + + + + + + + + + + + + Meta Horizon Packaging tester + + + +

+ PWABuilder Meta Horizon packaging service +

+
This service builds Android App Bundles (AAB) from your PWA, which can be uploaded to the Meta Horizon Developer Center for listing in the Meta Horizon Store.
+
+ +
+
+ JSON + Presets: + + + + + +
+
+ +
+ +
+ +
+
+ Results +
+
+

+    
+
+ + + + + + + + + diff --git a/apps/pwabuilder-meta-horizon/static/index.js b/apps/pwabuilder-meta-horizon/static/index.js new file mode 100644 index 000000000..dfbf2b248 --- /dev/null +++ b/apps/pwabuilder-meta-horizon/static/index.js @@ -0,0 +1,223 @@ +const unsignedBtn = document.querySelector("#unsignedBtn"); +const signedBtn = document.querySelector("#signedBtn"); +const chooseKeyBtn = document.querySelector("#chooseKeyBtn"); +const existingKeyBtn = document.querySelector("#existingKeyBtn"); +const filePicker = document.querySelector("#filePicker"); +const codeArea = document.querySelector("textarea"); +const submitBtn = document.querySelector("#submitBtn") +const resultsDiv = document.querySelector("#results"); +const spinner = document.querySelector(".spinner-border"); + +unsignedBtn.addEventListener("click", () => setCode(getUnsignedPackageOptions())); +signedBtn.addEventListener("click", () => setCode(getSignedPackage())); +existingKeyBtn.addEventListener("click", () => setCode(getExistingKeySignedPackage())) +chooseKeyBtn.addEventListener("click", () => filePicker.click()); +filePicker.addEventListener("change", (e) => keyFileChosen(e)); +submitBtn.addEventListener("click", () => submit()); + +setCode(getSignedPackage()); +codeArea.scrollTop = 0; +packagingStartMs = 0; + +function setCode(options) { + const code = JSON.stringify(options, undefined, 4); + codeArea.value = code; + codeArea.scrollTop = 1000000; +} + +function getUnsignedPackageOptions() { + return { + "additionalTrustedOrigins": [], + "appVersion": "1.0.0.0", + "appVersionCode": 1, + "backgroundColor": "#FFFFFF", + "display": "standalone", + "enableSiteSettingsShortcut": true, + "enableNotifications": false, + "enableMicrophone": false, + "enableXRScene": false, + "includeSourceCode": false, + "fallbackType": "customtabs", + "features": { + "locationDelegation": { + "enabled": true + }, + "horizonBilling": { + "enabled": false, + "horizonOSAppMode": "2D" + }, + "horizonPlatformSDK": { + "enabled": false + } + }, + "horizonOSAppMode": "2D", + "metaHorizonAppId": "0", + "host": "https://webboard.app", + "iconUrl": "https://webboard.app/icons/android/android-launchericon-512-512.png", + "launcherName": "Webboard", + "name": "Webboard", + "maskableIconUrl": "https://webboard.app/icons/android/maskable_icon_192.png", + "navigationColor": "#1976d2", + "navigationColorDark": "#1976d2", + "navigationDividerColor": "#1976d2", + "navigationDividerColorDark": "#1976d2", + "orientation": "default", + "packageId": "app.webboard", + "pwaUrl": "https://webboard.app", + "fullScopeUrl": "https://webboard.app/", + "minSdkVersion": 23, + "shortcuts": [ + { + "name": "Start Live Session", + "short_name": "Start Live", + "url": "/?startLive", + "icons": [ + { + "sizes": "192x192", + "src": "/icons/android/maskable_icon_192.png" + } + ] + } + ], + "signingMode": "none", + "signing": { + "file": null, + "alias": "my-android-key", + "fullName": "John Doe", + "organization": "Contoso", + "organizationalUnit": "Engineering Department", + "countryCode": "US", + "keyPassword": "aBc123$%", + "storePassword": "iOp987#@" + }, + "splashScreenFadeOutDuration": 300, + "startUrl": "/", + "themeColor": "#1976d2", + "themeColorDark": "#0d1117", + "webManifestUrl": "https://webboard.app/manifest.json" + }; +} + +function getSignedPackage() { + const options = getUnsignedPackageOptions(); + options.signingMode = "new"; + options.signing = { + file: null, + alias: "my-android-key", + fullName: "John Doe", + organization: "Contoso", + organizationalUnit: "Engineering Department", + countryCode: "US", + keyPassword: "aBc123$%", + storePassword: "iOp987#@" + }; + return options; +} + +function getExistingKeySignedPackage() { + const options = getSignedPackage(); + options.signingMode = "mine"; + options.signing.file = "data:application/octet-stream;base64,/u3+7QAAAAIAAAABAAAAAQAMbXkta2V5LWFsaWFzAAABczGxKzAAAAUBMIIE/TAOBgorBgEEASoCEQEBBQAEggTpI6J2fYdH5unUHLQGi6kqfeneUwE8qoTAKv9H/VRinYzE/UH8/jT1XakZZ7PzPUgM+FjziG/SaGn+Fw0o4brC2SqCDTAX+MR6YYDEmp8U8zmVLoGXq2wOWzvRZi6oGxHO287VcSlRWITTMHfQUrACNtKXBuvxkzEDjU4K1iFHzpiOODpOTtvWPmWAJ96aTD8D09KOKbqDQcTwwrEx5+WX3B/EerLMa0O5TSWJ/d+MyPeJiW8Rkz8rk+TkD/johGro8z5hgjYH4P+mK4M5IhAk/acYb6p0P4xDUVLbCpcfptQOt9DVzTD1HSELcSw2SKR3NlHjujLX5pJomr2NQj47qPOyi6SskFmmLaQ/8XCV7w9yTV/RV6/YFiv2zbr70CuAlGZbJoCNQJbSimS66hCI7O4xYMFgeY/5RcTHSz48M97tswIO8A8ehRmugWbju2abGiOoQpQWlYo/d12a8khIHELRvDM9xhdl4KSQld34yinp663aV7jsdDAg7tuAP69//WaJutsFM/KdLGdSgy9oRd6OOKtAH1Eqi5NWPVN0F5ILtDPksW/cVVc0qly8diJYvBXvpb5GLQWcsjfzhgxlzq9wGLjAbJJ+8Ez6wubbk2t6GNuZLZj2TGrUUEsgCPe9ULnJGQz+EKKSjBPZ8NY93DnEk1E5/K2FRs4x70LCSEXjFKUzK8AR3S4s7WK/2zlYJrQbnZEshdHvBfcP+8x5UM8ma6IEudKOJA/ttGJnW/qjncI6Oqg+DTLylbyXSBGMfAcDXYY6faPW3XmmPS2N/4kX1QycrGEHnT4NgexT57TwvRl1MsYhmt2suPGW5RNUE1TGoc2cdMKziPj976UFMOM5U4tyMKMbEuJICWxlO70O9iDB/PbN39Pepv5BhD3lHH2TEZCNdG80ThZw9s9JSzziLDppxuVMTZ23iNJkH5dqHt236uuSQxFszXo16qIWyhcD1C4H0PMSax2FeBxh/sDRpCIdhE7UrFpqL9OB/97L8Rp8uzWpDQg+dAWZ8E09DYlS9KNVcZJFKoPXGCWvJU6spmLMzbiX+McH5me3ouDBhJxt7xULgYwASNT+hL9IySLh1jbfpgXnKab0XvDxtfZ/+3nOOiTTfc44G4ai4ZizrCe4j59v5OLUL074Hc7tYYK08oX7EPH8aDyJsUy6iqj1XXjl7J3Kg7bWsJOHmiYiQaQDdTdi3k9rqEu+pWp0Y56FTjtrm4DKu9HaNMEfpWXrR/robkterwuiChFVFzKJXIZHF33pAOI0WJjUTEkSla4InOijVJGfZhneuc6Q1COwF/4Ha5wmKE4ZnWZWDcFHDM2cpcN6KU3D2ndjSyq+ni2uEm3glubP3UUHa2wNwL//9UP2hvyRoWj2tmSW0U0gSnu6/lfiM7bv2Msg8L4+xaN6EYFkAHgIG2AtApn4YPlfQkhucCPuc+Tjtw7E5E+daAeN5S3WBm0/Fggd6mBpVGK3620yzebRnIY4PQZM/7DT7pDXZA9v2zAlmBfHeG9XmMCMvFGlsrAZ50v8nAvXjgEzqfb9aiQM+S+FkuMtRvoV3DQrHbw7C4p7JYZPWaK7biX1PvkkcTOUif9FRAQxCffNTqgm2YJDkjr0dS8GAl2+MjhgGc/hgzX5ircoTEs0tWdaiwWlE6Zk3LgnjB5tgsaCgNXgDMnGAAAAAQAFWC41MDkAAANfMIIDWzCCAkOgAwIBAgIEXZe6IDANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJVUzETMBEGA1UEChMKcHdhYnVpbGRlcjEfMB0GA1UECxMWRW5naW5lZXJpbmcgRGVwYXJ0bWVudDEYMBYGA1UEAxMPUFdBQnVpbGRlciBVc2VyMCAXDTIwMDcwOTAzNDkyMVoYDzIwNzUwNDEyMDM0OTIxWjBdMQswCQYDVQQGEwJVUzETMBEGA1UEChMKcHdhYnVpbGRlcjEfMB0GA1UECxMWRW5naW5lZXJpbmcgRGVwYXJ0bWVudDEYMBYGA1UEAxMPUFdBQnVpbGRlciBVc2VyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqXnhK4AZ80RGrYotQQwmkqTJNh6L3SUE3AEAXgL73Jvtc/301d5zOWfPLdS137TEoeOGsa273c/jLi2J0pw5Qx6iNb9rebf2fE0xh/fYWYipXG2c/EE8hP6VGfMD1jLv2ciCSrS26aOGrGNcWDl6GUvYh3ZL98kN5E9ZhYGa3N+nT5SJXRVAlLQn9KdWbn3v71dhRIHFMDF1O+1OKNvy/raQMWupcGPJlvcoKn+istgbAiTzhYDeWnc7JxJzpZyJu3OIiG9mE3yS1zk3RzlInV1VCgSE/ePo5CXlYiYRluSmU7z2ePTg4f7xQOsHE4i+5yKPLe5QJ9lZ0TrG0qY2xwIDAQABoyEwHzAdBgNVHQ4EFgQUu1ubRq7PVR7b9UZGvkPmaeivqrcwDQYJKoZIhvcNAQELBQADggEBAJOUQGdQ8mA+dlsLsNHhLPLZvGpApbBlN/9ZEuArh/Sdwf77UtZNgjB2keCjhYcdJKc5Dd0c327qJGew3HN1ZE9YgjuM3NBtV0YYvOJhv3W7wcFJVZ8UPKTquc4fuROHL2OxsNQvvBbFypjfEb4QeYV/ro+CMUOrcjYEVLfk1nmMhyYjmEaazWefZUNcMpBK4LXECHsEugUbzuo4Pz5DuRcLjEb4s6MRcyI3sY2/ct/jDHZyUesQk63GpxrFDNq3+C50tD3xiukvKha6f6Fji8J1GFeNSEO/5DI/ED1TTg3ei/L9coqYWljXPeqCSFRu9a9VDYeV/wOABocTh8KDJ73ic0oKFN14vwPKvockFZiUSsgOSg=="; + options.signing.alias = "my-key-alias"; + options.signing.keyPassword = "52SO4fg9ZIsw"; + options.signing.storePassword = "o93619kHyx82"; + return options; +} + +function keyFileChosen(e) { + if (filePicker.files) { + const options = getSignedPackage(); + + const fileReader = new FileReader(); + fileReader.onload = () => { + options.file = fileReader.result; + setCode(options); + } + fileReader.readAsDataURL(filePicker.files[0]); + + filePicker.file = null; + } +} + +async function submit() { + resultsDiv.textContent = ""; + packagingStartMs = Date.now(); + + setLoading(true); + try { + const options = JSON.stringify(JSON.parse(codeArea.value)); + const response = await fetch("/enqueuePackageJob", { + method: "POST", + body: options, + headers: new Headers({ 'content-type': 'application/json', 'platform-identifier': 'ServerUI', 'platform-identifier-version': '1.0.0' }), + }); + + if (!response.ok) { + appendResults(`Failed to enqueue Meta Horizon packaging job. Status code ${response.status}, Error: ${response.statusText}.`); + setLoading(false); + return; + } + + const jobId = await response.text(); + appendResults(`Enqueued job ${jobId}. Waiting for processing...`); + setTimeout(() => this.pollJob(jobId), 2000); + } catch (err) { + appendResults(`Failed. Error: ${err}`); + setLoading(false); + } +} + +async function pollJob(jobId) { + const jobFetch = await fetch("/getPackageJob?id=" + encodeURIComponent(jobId)); + if (!jobFetch.ok) { + appendResults(`Failed to fetch job ${jobId}. Status code ${jobFetch.status}, Error: ${jobFetch.statusText}.`); + setLoading(false); + return; + } + + const job = await jobFetch.json(); + + if (job.status === "Completed") { + appendResults(`Job completed in ${Date.now() - packagingStartMs}ms. Triggering download...`); + downloadJobZip(jobId); + } else if (job.status === "Failed") { + appendResults(`Job failed in ${Date.now() - packagingStartMs}ms. See logs above for details.`); + setLoading(false); + } else { + setTimeout(() => this.pollJob(jobId), 2000); + job.logs.forEach(l => appendResultIfNotIncluded(l)); + } +} + +async function downloadJobZip(jobId) { + const zipFetch = await fetch("/downloadPackageZip?id=" + encodeURIComponent(jobId)); + if (zipFetch.ok) { + const data = await zipFetch.blob(); + const url = window.URL.createObjectURL(data); + window.location.assign(url); + setLoading(false); + } else { + appendResults(`Failed to download package zip for job ${jobId}. Status code ${zipFetch.status}, Error: ${zipFetch.statusText}.`); + setLoading(false); + } +} + +function appendResults(val) { + resultsDiv.textContent = [resultsDiv.textContent, val].join("\n"); +} + +function appendResultIfNotIncluded(line) { + const resultLines = resultsDiv.textContent.split("\n"); + if (!resultLines.includes(line)) { + appendResults(line); + } +} + +function setLoading(state) { + submitBtn.disabled = state; + if (state) { + spinner.classList.remove("d-none"); + } else { + spinner.classList.add("d-none"); + } +} diff --git a/apps/pwabuilder-meta-horizon/static/next-steps-unsigned.html b/apps/pwabuilder-meta-horizon/static/next-steps-unsigned.html new file mode 100644 index 000000000..aa11a357b --- /dev/null +++ b/apps/pwabuilder-meta-horizon/static/next-steps-unsigned.html @@ -0,0 +1,8 @@ + + + + + diff --git a/apps/pwabuilder-meta-horizon/static/next-steps-unsigned.md b/apps/pwabuilder-meta-horizon/static/next-steps-unsigned.md new file mode 100644 index 000000000..fe92fa19a --- /dev/null +++ b/apps/pwabuilder-meta-horizon/static/next-steps-unsigned.md @@ -0,0 +1,49 @@ +# Next steps for getting your PWA into the Meta Horizon Store +You've successfully generated a Meta Horizon Store app package (`.apk` file) for your PWA. 🥽 + +You chose to generate an *unsigned* package, so there are a few additional steps. If you'd rather generate a signed package, you can do so by re-running PWABuilder's Meta Horizon packaging dialog and choosing a signing mode. + +Your next steps: + +1. Sign your APK. +2. Generate `assetlinks.json` and deploy it to your server. +3. Test your package on a Meta Quest device or emulator. +4. Upload your `.aab` file to the Meta Horizon Store. + +Each step is explained below. + +## 1. Sign your APK + +You instructed PWABuilder to generate an *unsigned* APK. Before you can submit to the Horizon Store, you'll need to sign it. + +To sign your APK, you can either re-run PWABuilder and ask it to generate a signed APK / AAB, or you can [manually sign it](https://developer.android.com/studio/publish/app-signing) with a new or existing key. + +💁‍♂️ *Heads up*: If you create a new signing key, make sure you keep it and its credentials in a safe place. You'll need it again to upload future versions of your app to the Meta Horizon Store. + +## 2. Generate and deploy `assetlinks.json` + +While this step is optional, skipping it will cause the Quest browser to display a URL bar inside your app. + +A [Digital Asset Links](https://developers.google.com/web/updates/2019/08/twas-quickstart#creating-your-asset-link-file) file proves you own your PWA's domain. + +Once you've signed your APK, you can use the signing key to generate `assetlinks.json` - your digital asset links file. + +Once generated, upload it to your server at `https://example.com/.well-known/assetlinks.json`. (Replace example.com with your PWA's URL.) + +💁‍♂️ *Heads up*: **Digital asset links are required for your PWA to load without a URL bar inside the Trusted Web Activity**. + +## 3. Test your APK on a Meta Quest device + +Once you sign your `.apk` file, you can sideload it onto a Meta Quest headset for testing. + +You can use [Meta Quest Developer Hub](https://developers.meta.com/horizon/documentation/) or `adb install` to push your APK to the headset. + +## 4. Upload your `.aab` file to the Meta Horizon Store + +Your signed `.aab` file can be submitted via the [Meta Horizon Developer Center](https://developers.meta.com/horizon/). + +For full guidance on submission, see the [Meta Horizon PWA packaging documentation](https://developers.meta.com/horizon/documentation/web/pwa-packaging/). + +## Need more help? + +If you're stuck and need help, we're here to help. You can [open an issue](https://github.com/pwa-builder/pwabuilder/issues) and we'll help walk you through it. diff --git a/apps/pwabuilder-meta-horizon/static/next-steps.html b/apps/pwabuilder-meta-horizon/static/next-steps.html new file mode 100644 index 000000000..ad95e8f97 --- /dev/null +++ b/apps/pwabuilder-meta-horizon/static/next-steps.html @@ -0,0 +1,7 @@ + + + + + diff --git a/apps/pwabuilder-meta-horizon/tsconfig.json b/apps/pwabuilder-meta-horizon/tsconfig.json new file mode 100644 index 000000000..4fbe283db --- /dev/null +++ b/apps/pwabuilder-meta-horizon/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + /* Basic Options */ + "incremental": true /* Enable incremental compilation */, + "target": "ES2022" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */, + "module": "Node16" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, + "moduleResolution": "node16" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, + "lib": [ + "ES2022", + "DOM" + ] /* Specify library files to be included in the compilation. */, + "allowJs": false /* Allow javascript files to be compiled. */, + "sourceMap": true /* Generates corresponding '.map' file. */, + + /* Strict Type-Checking Options */ + "strict": true /* Enable all strict type-checking options. */, + "strictNullChecks": true /* Enable strict null checks. */, + "skipLibCheck": true, + + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, + + /* Advanced Options */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + }, + "include": ["**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/apps/pwabuilder-meta-horizon/utils/base64ToBuffer.ts b/apps/pwabuilder-meta-horizon/utils/base64ToBuffer.ts new file mode 100644 index 000000000..c0126f9d1 --- /dev/null +++ b/apps/pwabuilder-meta-horizon/utils/base64ToBuffer.ts @@ -0,0 +1,8 @@ +export function base64ToBuffer(base64: string): Buffer { + const matches = base64.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/); + if (!matches || matches.length !== 3) { + throw new Error('Invalid base 64 string'); + } + + return Buffer.from(matches[2], 'base64'); +} diff --git a/apps/pwabuilder-meta-horizon/utils/errorToString.ts b/apps/pwabuilder-meta-horizon/utils/errorToString.ts new file mode 100644 index 000000000..bfc52153c --- /dev/null +++ b/apps/pwabuilder-meta-horizon/utils/errorToString.ts @@ -0,0 +1,23 @@ +export function errorToString(error: any): string { + if (!error) { + return "Unknown error"; + } + + let errorMessage = error.message || `${error}`; + + // Enrich the error with additional information from stack, stderr, cmd, code + if (error.cmd) { + errorMessage += `\r\ncmd: ${error.cmd}`; + } + if (error.code !== null && error.code !== undefined) { + errorMessage += `\r\ncode: ${error.code}`; + } + + const stdErrorOrStack = error.stderr || error.stack; + if (stdErrorOrStack && !errorMessage.includes(stdErrorOrStack)) { + const label = error.stderr ? "stderr" : "stack" + errorMessage += `\r\n${label}: ${error.stderr || error.stack}`; + } + + return errorMessage; +} diff --git a/apps/pwabuilder-meta-horizon/utils/hashCode.ts b/apps/pwabuilder-meta-horizon/utils/hashCode.ts new file mode 100644 index 000000000..8a2681674 --- /dev/null +++ b/apps/pwabuilder-meta-horizon/utils/hashCode.ts @@ -0,0 +1,10 @@ +export function createHash(obj: any) { + const str = JSON.stringify(obj); + let hash = 0; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; // Convert to 32-bit integer + } + return hash; +} diff --git a/apps/pwabuilder-meta-horizon/utils/msToFriendly.ts b/apps/pwabuilder-meta-horizon/utils/msToFriendly.ts new file mode 100644 index 000000000..6b8637dbe --- /dev/null +++ b/apps/pwabuilder-meta-horizon/utils/msToFriendly.ts @@ -0,0 +1,10 @@ +export function msToFriendly(ms: number): string { + const sec = Math.floor((ms / 1000) % 60); + const min = Math.floor((ms / (1000 * 60)) % 60); + const hr = Math.floor((ms / (1000 * 60 * 60)) % 24); + const parts = []; + if (hr) parts.push(`${hr} hour${hr !== 1 ? 's' : ''}`); + if (min) parts.push(`${min} minute${min !== 1 ? 's' : ''}`); + if (sec || parts.length === 0) parts.push(`${sec} second${sec !== 1 ? 's' : ''}`); + return parts.join(', '); +} diff --git a/apps/pwabuilder/Frontend/src/app-index.ts b/apps/pwabuilder/Frontend/src/app-index.ts index 32ec69e8e..9d2de96c0 100644 --- a/apps/pwabuilder/Frontend/src/app-index.ts +++ b/apps/pwabuilder/Frontend/src/app-index.ts @@ -187,6 +187,11 @@ export class AppIndex extends LitElement { component: 'google-play-packaging-status', action: async () => await import('./script/pages/google-play-packaging-status.js') }, + { + path: '/meta-horizon-packaging-status', + component: 'meta-horizon-packaging-status', + action: async () => await import('./script/pages/meta-horizon-packaging-status.js') + }, { path: '(.*)', // Match any other route not defined above redirect: '/', // Redirect to the home page or another valid route diff --git a/apps/pwabuilder/Frontend/src/script/components/oculus-form.ts b/apps/pwabuilder/Frontend/src/script/components/oculus-form.ts new file mode 100644 index 000000000..aafe57d2e --- /dev/null +++ b/apps/pwabuilder/Frontend/src/script/components/oculus-form.ts @@ -0,0 +1,938 @@ +import { css, html, PropertyValueMap, TemplateResult } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; +import { fetchOrCreateManifest } from '../services/manifest'; +import { + createMetaHorizonPackageOptionsFromManifest, + emptyMetaHorizonPackageOptions, +} from '../services/publish/meta-horizon-publish'; +import { ManifestContext, PackageOptions } from '../utils/interfaces'; +import { AppPackageFormBase } from './app-package-form-base'; +import { getManifestContext } from '../services/app-info'; +import { maxSigningKeySizeInBytes, validateAndroidPackageId } from '../utils/android-validation'; +import { recordPWABuilderProcessStep, AnalyticsBehavior } from '../utils/analytics'; +import { AppNameInputPattern, DnameInputPattern } from '../utils/constants'; +import '@shoelace-style/shoelace/dist/components/details/details.js'; + +@customElement('oculus-form') +export class OculusForm extends AppPackageFormBase { + @property({ attribute: 'analysis-id' }) analysisId: string | null = null; + @property({ type: Boolean }) generating = false; + @state() showAdvanced = false; + @state() packageOptions = emptyMetaHorizonPackageOptions(); + @state() manifestContext: ManifestContext | undefined = getManifestContext(); + + static get styles() { + const localStyles = css` + :host { + width: 100%; + } + + #oculus-options-form { + width: 100%; + display: flex; + flex-direction: column; + } + + .signing-key-fields { + margin-left: 30px; + } + + #signing-key-file-input { + border: none; + } + + .form-generate-button { + width: 135px; + height: 40px; + } + + .basic-settings, .adv-settings { + display: flex; + flex-direction: column; + gap: .75em; + } + #form-layout { + flex-grow: 1; + display: flex; + overflow: auto; + flex-direction: column; + } + + sl-details { + margin-top: 1em; + } + + sl-details::part(base) { + border: none; + } + + sl-details::part(summary-icon) { + display: none; + } + + .dropdown_icon { + transform: rotate(0deg); + transition: transform .5s; + height: 30px; + } + + sl-details::part(header) { + padding: 0 10px; + } + .details-summary { + display: flex; + align-items: center; + width: 100%; + } + .details-summary p { + margin: 0; + font-size: 18px; + font-weight: bold; + } + `; + + return [ + ...super.styles, + localStyles, + ]; + } + + constructor() { + super(); + } + + async firstUpdated() { + if (this.manifestContext!.isGenerated) { + this.manifestContext = await fetchOrCreateManifest(); + } + + this.packageOptions = createMetaHorizonPackageOptionsFromManifest(this.manifestContext!); + } + + protected updated(_changedProperties: PropertyValueMap | Map): void { + // Nothing currently — the Meta Horizon form has no toggle equivalent to Google Play vs Other Android. + void _changedProperties; + } + + metaHorizonSigningModeChanged(mode: 'mine' | 'new' | 'none') { + this.packageOptions.signingMode = mode; + const signing = this.packageOptions.signing!; + + if (mode === 'mine' || mode === 'none') { + signing.alias = ''; + signing.fullName = ''; + signing.organization = ''; + signing.organizationalUnit = ''; + signing.countryCode = ''; + signing.keyPassword = ''; + signing.storePassword = ''; + } else if (mode === 'new') { + signing.alias = 'my-key-alias'; + signing.fullName = (this.manifestContext!.manifest.short_name || this.manifestContext!.manifest.name || 'My PWA') + ' Admin'; + signing.organization = this.manifestContext!.manifest.name || 'PWABuilder'; + signing.organizationalUnit = 'Engineering'; + signing.countryCode = 'US'; + signing.keyPassword = ''; + signing.storePassword = ''; + signing.file = null; + } + + this.requestUpdate(); + } + + horizonAppModeChanged(mode: '2D' | 'immersive') { + this.packageOptions.horizonOSAppMode = mode; + // Keep horizonBilling.horizonOSAppMode in sync with the top-level value. + if (this.packageOptions.features.horizonBilling) { + this.packageOptions.features.horizonBilling.horizonOSAppMode = mode; + } + this.requestUpdate(); + } + + validatePackageId(packageId: string, input: HTMLInputElement) { + const validationErrors = validateAndroidPackageId(packageId); + const errorMessage = validationErrors.length > 0 ? validationErrors[0].error : ''; + input.setCustomValidity(errorMessage); + input.title = errorMessage; + if (errorMessage) { + input.reportValidity(); + } + } + + metaHorizonSigningKeyUploaded(event: any) { + const filePicker = event as HTMLInputElement; + if (filePicker && filePicker.files && filePicker.files.length > 0) { + const keyFile = filePicker.files[0]; + if (keyFile && keyFile.size > maxSigningKeySizeInBytes) { + console.error('Meta Horizon signing key file is too large. Max size:', { + maxSize: maxSigningKeySizeInBytes, + fileSize: keyFile.size, + }); + this.packageOptions.signingMode = 'none'; + } + const fileReader = new FileReader(); + fileReader.onload = () => { + this.packageOptions.signing!.file = fileReader.result as string; + }; + fileReader.onerror = progressEvent => { + console.error('Unable to read Meta Horizon signing key file', fileReader.error, progressEvent); + this.packageOptions.signing!.file = null; + this.packageOptions.signingMode = 'none'; + }; + fileReader.readAsDataURL(keyFile as Blob); + } + } + + rotateZero() { + recordPWABuilderProcessStep('meta_horizon_form_all_settings_expanded', AnalyticsBehavior.ProcessCheckpoint); + const icon: any = this.shadowRoot!.querySelector('.dropdown_icon'); + icon!.style.transform = 'rotate(0deg)'; + } + + rotateNinety() { + recordPWABuilderProcessStep('meta_horizon_form_all_settings_collapsed', AnalyticsBehavior.ProcessCheckpoint); + const icon: any = this.shadowRoot!.querySelector('.dropdown_icon'); + icon!.style.transform = 'rotate(90deg)'; + } + + public getPackageOptions(): PackageOptions { + return this.packageOptions; + } + + public getForm(): HTMLFormElement { + return this.shadowRoot!.querySelector('form')!; + } + + render() { + return html` +
+
+
+
+ +
+ ${this.renderFormInput({ + label: 'Package ID', + tooltip: `The Android applicationId for your Meta Horizon app, e.g. com.companyname.appname. Letters, numbers, periods, hyphens, and underscores are allowed.`, + tooltipLink: 'https://developers.meta.com/horizon/documentation/web/pwa-packaging/', + inputId: 'package-id-input', + required: true, + placeholder: 'com.companyname.appname', + value: this.packageOptions.packageId, + minLength: 3, + maxLength: Number.MAX_SAFE_INTEGER, + spellcheck: false, + pattern: "^[a-zA-Z0-9.\\-_]*$", + validationErrorMessage: 'Package ID must contain only letters, numbers, periods, hyphens, and underscores.', + changedHandler: (val: string, _: boolean, input: HTMLInputElement) => { + this.packageOptions.packageId = val; + this.validatePackageId(val, input); + } + })} +
+ +
+ ${this.renderFormInput({ + label: 'App name', + tooltip: `The full name of your app as displayed to end users on Meta Horizon.`, + inputId: 'app-name-input', + required: true, + placeholder: 'My Awesome PWA', + value: this.packageOptions.name, + minLength: 3, + maxLength: 50, + spellcheck: false, + pattern: AppNameInputPattern, + validationErrorMessage: 'App name must not include special characters and be between 3 and 50 characters', + inputHandler: (val: string) => this.packageOptions.name = val, + })} +
+ +
+ ${this.renderFormInput({ + label: 'Short name', + tooltip: `Shorter version of your app name displayed on the Meta Horizon launcher.`, + inputId: 'short-name-input', + required: true, + placeholder: 'My PWA', + value: this.packageOptions.launcherName, + minLength: 3, + maxLength: 30, + spellcheck: false, + validationErrorMessage: 'Short name must be between 3 and 30 characters in length.', + inputHandler: (val: string) => this.packageOptions.launcherName = val, + })} +
+ +
+ ${this.renderFormInput({ + label: 'Meta Horizon app ID', + tooltip: `The numeric Meta Horizon application ID from the Meta Developer Dashboard. Required if Horizon Billing is enabled. See the URL when viewing your app on developers.meta.com/horizon/manage/applications//.`, + tooltipLink: 'https://developers.meta.com/horizon/documentation/web/pwa-packaging/', + inputId: 'meta-horizon-app-id-input', + placeholder: '1234567890', + value: this.packageOptions.metaHorizonAppId || '', + spellcheck: false, + pattern: '^[0-9]*$', + validationErrorMessage: 'Meta Horizon app ID must be a numeric string from the Meta Developer Dashboard.', + inputHandler: (val: string) => this.packageOptions.metaHorizonAppId = val, + })} +
+ +
+ +
+ ${this.renderFormInput({ + label: 'Immersive (WebXR)', + tooltip: 'Run the PWA as an immersive WebXR experience inside Horizon OS. Recommended for VR experiences.', + inputId: 'horizon-app-mode-immersive-input', + type: 'radio', + name: 'horizonOSAppMode', + value: 'immersive', + checked: this.packageOptions.horizonOSAppMode === 'immersive', + inputHandler: () => this.horizonAppModeChanged('immersive'), + })} +
+
+ ${this.renderFormInput({ + label: '2D (Flat panel)', + tooltip: 'Run the PWA as a flat 2D panel app inside Horizon OS.', + inputId: 'horizon-app-mode-2d-input', + type: 'radio', + name: 'horizonOSAppMode', + value: '2D', + checked: this.packageOptions.horizonOSAppMode === '2D', + inputHandler: () => this.horizonAppModeChanged('2D'), + })} +
+
+ +
+ +
+ ${this.renderFormInput({ + label: 'Enable', + tooltip: 'If enabled, your download will include the source code for the generated Meta Horizon Android project.', + inputId: 'include-src-input', + type: 'checkbox', + checked: this.packageOptions.includeSourceCode === true, + inputHandler: (_, checked) => this.packageOptions.includeSourceCode = checked, + })} +
+
+
+ + this.rotateNinety()} @sl-hide=${() => this.rotateZero()}> +
+

All Settings

+ dropdown toggler +
+
+ +
+ ${this.renderFormInput({ + label: 'Host', + tooltip: `The host portion of your PWA's URL, e.g. mypwa.com.`, + inputId: 'host-input', + required: true, + placeholder: 'mypwa.com', + value: this.packageOptions.host, + minLength: 3, + spellcheck: false, + inputHandler: (val: string) => this.packageOptions.host = val, + })} +
+ +
+ ${this.renderFormInput({ + label: 'Start URL', + tooltip: `The start path for your PWA. Must be relative to Host. Use '/' if your start URL is the same as Host.`, + inputId: 'start-url-input', + required: true, + placeholder: '/index.html', + value: this.packageOptions.startUrl, + spellcheck: false, + validationErrorMessage: "You must specify a relative start URL. If you don't have a start URL, use '/'", + inputHandler: (val: string) => this.packageOptions.startUrl = val, + })} +
+ +
+ ${this.renderFormInput({ + label: 'Full scope URL', + tooltip: `Required for Meta Horizon. The absolute URL of your PWA's scope. Navigations outside this scope open in a browser tab.`, + inputId: 'full-scope-url-input', + type: 'url', + required: true, + value: this.packageOptions.fullScopeUrl, + placeholder: 'https://mypwa.com/', + inputHandler: (val: string) => this.packageOptions.fullScopeUrl = val, + })} +
+ +
+ ${this.renderFormInput({ + label: 'Version', + tooltip: `The version of your app displayed to users (e.g. '1.0.0.0'). Maps to android:versionName.`, + inputId: 'version-input', + required: true, + placeholder: '1.0.0.0', + value: this.packageOptions.appVersion, + spellcheck: false, + inputHandler: (val: string) => this.packageOptions.appVersion = val, + })} +
+ +
+ ${this.renderFormInput({ + label: 'Version code', + tooltip: `Internal version number used by the Horizon Store to determine update ordering. Maps to android:versionCode.`, + inputId: 'version-code-input', + type: 'number', + minValue: 1, + maxValue: 2100000000, + required: true, + placeholder: '1', + value: this.packageOptions.appVersionCode.toString(), + inputHandler: (val: string) => this.packageOptions.appVersionCode = parseInt(val, 10), + })} +
+ +
+ ${this.renderFormInput({ + label: 'Theme color', + tooltip: `The theme color for your app. Typically set to your manifest's theme_color.`, + inputId: 'theme-color-input', + type: 'color', + value: this.packageOptions.themeColor, + inputHandler: (val: string) => this.packageOptions.themeColor = val, + })} +
+ +
+ ${this.renderFormInput({ + label: 'Theme dark color', + tooltip: `The theme color used in dark mode.`, + inputId: 'theme-dark-color-input', + type: 'color', + value: this.packageOptions.themeColorDark, + inputHandler: (val: string) => this.packageOptions.themeColorDark = val, + })} +
+ +
+ ${this.renderFormInput({ + label: 'Background color', + tooltip: `The background color used for your app's splash screen. Typically set to your manifest's background_color.`, + inputId: 'background-color-input', + type: 'color', + value: this.packageOptions.backgroundColor, + inputHandler: (val: string) => this.packageOptions.backgroundColor = val, + })} +
+ +
+ ${this.renderFormInput({ + label: 'Nav color', + tooltip: `Color of the navigation bar.`, + inputId: 'nav-color-input', + type: 'color', + value: this.packageOptions.navigationColor, + inputHandler: (val: string) => this.packageOptions.navigationColor = val, + })} +
+ +
+ ${this.renderFormInput({ + label: 'Nav dark color', + tooltip: `Color of the navigation bar in dark mode.`, + inputId: 'nav-dark-color-input', + type: 'color', + value: this.packageOptions.navigationColorDark, + inputHandler: (val: string) => this.packageOptions.navigationColorDark = val, + })} +
+ +
+ ${this.renderFormInput({ + label: 'Nav divider color', + tooltip: `Color of the navigation bar divider.`, + inputId: 'nav-divider-color-input', + type: 'color', + value: this.packageOptions.navigationDividerColor, + inputHandler: (val: string) => this.packageOptions.navigationDividerColor = val, + })} +
+ +
+ ${this.renderFormInput({ + label: 'Nav divider dark color', + tooltip: `Color of the navigation bar divider in dark mode.`, + inputId: 'nav-divider-dark-color-input', + type: 'color', + value: this.packageOptions.navigationDividerColorDark, + inputHandler: (val: string) => this.packageOptions.navigationDividerColorDark = val, + })} +
+ +
+ ${this.renderFormInput({ + label: 'Icon URL', + tooltip: `The URL to a square PNG image (512x512 or larger recommended) to use for your app icon. Can be absolute or relative to your manifest.`, + inputId: 'icon-url-input', + required: true, + spellcheck: false, + value: this.packageOptions.iconUrl, + placeholder: '/icons/512x512.png', + inputHandler: (val: string) => this.packageOptions.iconUrl = val, + })} +
+ +
+ ${this.renderFormInput({ + label: 'Maskable icon URL', + tooltip: `Optional. The URL to a maskable PNG icon for adaptive icon shapes.`, + inputId: 'maskable-icon-url-input', + spellcheck: false, + value: this.packageOptions.maskableIconUrl || '', + placeholder: '/icons/512x512-maskable.png', + inputHandler: (val: string) => this.packageOptions.maskableIconUrl = val, + })} +
+ +
+ ${this.renderFormInput({ + label: 'Monochrome icon URL', + tooltip: `Optional. The URL to a monochrome PNG icon used in notifications and themed icons.`, + inputId: 'monochrome-icon-url-input', + spellcheck: false, + value: this.packageOptions.monochromeIconUrl || '', + placeholder: '/icons/512x512-monochrome.png', + inputHandler: (val: string) => this.packageOptions.monochromeIconUrl = val, + })} +
+ +
+ ${this.renderFormInput({ + label: 'Manifest URL', + tooltip: `The absolute HTTPS URL of your web manifest.`, + inputId: 'manifest-url-input', + type: 'url', + value: this.packageOptions.webManifestUrl, + placeholder: 'https://myawesomepwa.com/manifest.json', + inputHandler: (val: string) => this.packageOptions.webManifestUrl = val, + })} +
+ +
+ ${this.renderFormInput({ + label: 'Splash fade out duration (ms)', + tooltip: `How long the splash screen fade out animation should last in milliseconds.`, + inputId: 'splash-fade-out-input', + type: 'number', + value: (this.packageOptions.splashScreenFadeOutDuration || 0).toString(), + placeholder: '300', + inputHandler: (val: string) => this.packageOptions.splashScreenFadeOutDuration = parseInt(val, 10), + })} +
+ +
+ +
+ ${this.renderFormInput({ + label: 'Enable', + tooltip: 'Show a Settings entry from the launcher long-press menu so users can manage your app.', + inputId: 'site-settings-shortcut-input', + type: 'checkbox', + checked: this.packageOptions.enableSiteSettingsShortcut === true, + inputHandler: (_, checked) => this.packageOptions.enableSiteSettingsShortcut = checked, + })} +
+
+ +
+ +
+ ${this.renderFormInput({ + label: 'Standalone', + tooltip: 'Recommended for most apps. Shows the system status / nav bars.', + inputId: 'display-standalone-input', + type: 'radio', + name: 'displayMode', + value: 'standalone', + checked: this.packageOptions.display === 'standalone', + inputHandler: () => this.packageOptions.display = 'standalone', + })} +
+
+ ${this.renderFormInput({ + label: 'Fullscreen', + tooltip: `Hides the system bars while your app is running. Suitable for games and immersive media.`, + inputId: 'display-fullscreen-input', + type: 'radio', + name: 'displayMode', + value: 'fullscreen', + checked: this.packageOptions.display === 'fullscreen', + inputHandler: () => this.packageOptions.display = 'fullscreen', + })} +
+
+ ${this.renderFormInput({ + label: 'Fullscreen sticky', + tooltip: `Hides the system bars but lets edge-swipes show them semi-transparently. Recommended for drawing and swipe-heavy games.`, + inputId: 'display-fullscreen-sticky-input', + type: 'radio', + name: 'displayMode', + value: 'fullscreen-sticky', + checked: this.packageOptions.display === 'fullscreen-sticky', + inputHandler: () => this.packageOptions.display = 'fullscreen-sticky', + })} +
+
+ +
+ +
+ ${this.renderFormInput({ + label: 'Enable', + tooltip: `If enabled, your PWA can send push notifications without browser permission prompts.`, + inputId: 'notification-delegation-input', + type: 'checkbox', + checked: this.packageOptions.enableNotifications === true, + inputHandler: (_, checked) => this.packageOptions.enableNotifications = checked, + })} +
+
+ +
+ +
+ ${this.renderFormInput({ + label: 'Enable', + tooltip: `Grants the Android microphone permission. Useful for PWAs using Web Speech / WebRTC inside the Quest browser.`, + inputId: 'enable-microphone-input', + type: 'checkbox', + checked: this.packageOptions.enableMicrophone === true, + inputHandler: (_, checked) => this.packageOptions.enableMicrophone = checked, + })} +
+
+ +
+ +
+ ${this.renderFormInput({ + label: 'Enable', + tooltip: `Grants the Horizon OS USE_SCENE permission so your PWA can use the WebXR scene mesh and spatial anchors.`, + inputId: 'enable-xr-scene-input', + type: 'checkbox', + checked: this.packageOptions.enableXRScene === true, + inputHandler: (_, checked) => this.packageOptions.enableXRScene = checked, + })} +
+
+ +
+ +
+ ${this.renderFormInput({ + label: 'Enable', + tooltip: 'If enabled, your PWA can access navigator.geolocation without browser permission prompts.', + inputId: 'location-delegation-input', + type: 'checkbox', + checked: this.packageOptions.features.locationDelegation?.enabled === true, + inputHandler: (_, checked) => this.packageOptions.features.locationDelegation!.enabled = checked, + })} +
+
+ +
+ +
+ ${this.renderFormInput({ + label: 'Enable', + tooltip: 'Enable Meta Horizon Store in-app purchases. Requires a Meta Horizon app ID above.', + inputId: 'horizon-billing-input', + type: 'checkbox', + checked: this.packageOptions.features.horizonBilling?.enabled === true, + inputHandler: (_, checked) => { + this.packageOptions.features.horizonBilling!.enabled = checked; + this.requestUpdate(); + }, + })} +
+
+ +
+ +
+ ${this.renderFormInput({ + label: 'Enable', + tooltip: 'Enable the Horizon Platform SDK for access to Quest platform APIs.', + inputId: 'horizon-platform-sdk-input', + type: 'checkbox', + checked: this.packageOptions.features.horizonPlatformSDK?.enabled === true, + inputHandler: (_, checked) => this.packageOptions.features.horizonPlatformSDK!.enabled = checked, + })} +
+
+ +
+ +
+ ${this.renderFormInput({ + label: 'Enable', + tooltip: 'Enable ARCore support for the generated Android project.', + inputId: 'arcore-input', + type: 'checkbox', + checked: this.packageOptions.features.arCore?.enabled === true, + inputHandler: (_, checked) => this.packageOptions.features.arCore!.enabled = checked, + })} +
+
+ +
+ +
+ ${this.renderFormInput({ + label: 'New', + tooltip: `PWABuilder will generate a new signing key and sign your package. Your download will contain the new signing details.`, + inputId: 'signing-new-input', + name: 'signingMode', + value: 'new', + type: 'radio', + checked: this.packageOptions.signingMode === 'new', + inputHandler: () => this.metaHorizonSigningModeChanged('new'), + })} +
+
+ ${this.renderFormInput({ + label: 'Use mine', + tooltip: 'Use this option if you already have a signing key and want to publish a new version of an existing app on Meta Horizon.', + inputId: 'signing-mine-input', + name: 'signingMode', + value: 'mine', + type: 'radio', + checked: this.packageOptions.signingMode === 'mine', + inputHandler: () => this.metaHorizonSigningModeChanged('mine'), + })} +
+
+ ${this.renderFormInput({ + label: 'None', + tooltip: 'Generate a raw, unsigned package. Unsigned packages cannot be uploaded to the Meta Horizon Store.', + inputId: 'signing-none-input', + name: 'signingMode', + value: 'none', + type: 'radio', + checked: this.packageOptions.signingMode === 'none', + inputHandler: () => this.metaHorizonSigningModeChanged('none'), + })} +
+
+ + ${this.renderSigningKeyFields()} + +
+ +
+ ${this.renderFormInput({ + label: 'Custom Tabs', + tooltip: `When Trusted Web Activity (TWA) is unavailable, use Chrome Custom Tabs as a fallback to run your app.`, + inputId: 'chrome-custom-tab-fallback-input', + type: 'radio', + name: 'fallbackType', + value: 'customtabs', + checked: this.packageOptions.fallbackType === 'customtabs', + inputHandler: () => this.packageOptions.fallbackType = 'customtabs', + })} +
+
+ ${this.renderFormInput({ + label: 'Web View', + tooltip: `When Trusted Web Activity (TWA) is unavailable, use a web view as a fallback to run your app.`, + inputId: 'web-view-fallback-input', + type: 'radio', + name: 'fallbackType', + value: 'webview', + checked: this.packageOptions.fallbackType === 'webview', + inputHandler: () => this.packageOptions.fallbackType = 'webview', + })} +
+
+
+
+
+
+
+ `; + } + + renderSigningKeyFields(): TemplateResult { + if (this.packageOptions.signingMode === 'new') { + return this.renderNewSigningKeyFields(); + } + if (this.packageOptions.signingMode === 'mine') { + return this.renderExistingSigningKeyFields(); + } + return html``; + } + + renderNewSigningKeyFields(): TemplateResult { + const signing = this.packageOptions.signing!; + return html` +
+
+ ${this.renderKeyAlias()} +
+ +
+ ${this.renderFormInput({ + label: 'Key full name', + tooltip: `Your full name. Used when creating the new signing key.`, + inputId: 'key-full-name-input', + required: true, + placeholder: 'John Doe', + value: signing.fullName || '', + pattern: DnameInputPattern, + spellcheck: false, + inputHandler: (val: string) => signing.fullName = val, + })} +
+ +
+ ${this.renderFormInput({ + label: 'Key organization', + tooltip: `The name of your company or organization.`, + inputId: 'key-org-input', + required: true, + placeholder: 'My Company', + value: signing.organization || '', + pattern: DnameInputPattern, + spellcheck: false, + inputHandler: (val: string) => signing.organization = val, + })} +
+ +
+ ${this.renderFormInput({ + label: 'Key organizational unit', + tooltip: `The department you work in, e.g. "Engineering".`, + inputId: 'key-org-unit-input', + required: true, + placeholder: 'Engineering Department', + value: signing.organizationalUnit, + pattern: DnameInputPattern, + spellcheck: false, + inputHandler: (val: string) => signing.organizationalUnit = val, + })} +
+ +
+ ${this.renderFormInput({ + label: 'Key country code', + tooltip: `Your country's 2 letter code (e.g. "US").`, + inputId: 'key-country-code-input', + required: true, + placeholder: 'US', + value: signing.countryCode, + pattern: DnameInputPattern, + spellcheck: false, + minLength: 2, + maxLength: 2, + validationErrorMessage: 'Country code must be 2 characters in length', + inputHandler: (val: string) => signing.countryCode = val, + })} +
+
+ `; + } + + renderExistingSigningKeyFields(): TemplateResult { + return html` +
+
+ + +
+ ${this.renderKeyAlias()} + ${this.renderKeyPassword()} + ${this.renderKeyStorePassword()} +
+ `; + } + + renderKeyAlias(): TemplateResult { + const tooltip = this.packageOptions.signingMode === 'new' + ? 'The alias to use in the new signing key.' + : 'The alias of your existing signing key.'; + const signing = this.packageOptions.signing!; + return html` +
+ ${this.renderFormInput({ + label: 'Key alias', + tooltip: tooltip, + inputId: 'key-alias-input', + required: true, + placeholder: 'my-key-alias', + value: signing.alias || '', + spellcheck: false, + inputHandler: (val: string) => signing.alias = val, + })} +
+ `; + } + + renderKeyPassword(): TemplateResult { + const signing = this.packageOptions.signing!; + const tooltip = this.packageOptions.signingMode === 'new' + ? 'Optional. Leave blank to let PWABuilder generate a strong password for you.' + : 'Your existing key password'; + return html` +
+ ${this.renderFormInput({ + label: 'Key password', + tooltip: tooltip, + inputId: 'key-password-input', + type: 'password', + minLength: 6, + required: this.packageOptions.signingMode === 'mine', + value: signing.keyPassword, + inputHandler: (val: string) => signing.keyPassword = val, + })} +
+ `; + } + + renderKeyStorePassword(): TemplateResult { + const signing = this.packageOptions.signing!; + const tooltip = this.packageOptions.signingMode === 'new' + ? 'Optional. Leave blank to let PWABuilder generate a strong key store password for you.' + : 'Your existing key store password'; + return html` +
+ ${this.renderFormInput({ + label: 'Key store password', + tooltip: tooltip, + inputId: 'key-store-password-input', + type: 'password', + minLength: 6, + required: this.packageOptions.signingMode === 'mine', + value: signing.storePassword, + inputHandler: (val: string) => signing.storePassword = val, + })} +
+ `; + } +} diff --git a/apps/pwabuilder/Frontend/src/script/components/publish-pane.ts b/apps/pwabuilder/Frontend/src/script/components/publish-pane.ts index 189826b5e..3cc67f4b4 100644 --- a/apps/pwabuilder/Frontend/src/script/components/publish-pane.ts +++ b/apps/pwabuilder/Frontend/src/script/components/publish-pane.ts @@ -22,13 +22,17 @@ import { import './windows-form'; import './android-form'; import './ios-form'; +import './oculus-form'; import { AppPackageFormBase } from './app-package-form-base'; import { PackageOptions } from '../utils/interfaces'; import { classMap } from 'lit/directives/class-map.js'; import { getDataFromDB, setDataInDB } from '../utils/indexedDB'; import { GooglePlayPackageError } from "../models/google-play-package-error"; +import { MetaHorizonPackageError } from "../models/meta-horizon-package-error"; import { enqueueGooglePlayPackageJob } from "../services/publish/android-publish"; +import { enqueueMetaHorizonPackageJob } from "../services/publish/meta-horizon-publish"; import { AndroidPackageOptions } from "../utils/android-validation"; +import { MetaHorizonPackageOptions } from "../utils/meta-horizon-validation"; import { Router } from '@vaadin/router'; import { AppStore, packagingCompleted, packagingFailed, packagingStarted } from '../pages/app-report.api'; @@ -73,6 +77,12 @@ export class PublishPane extends LitElement { "logo": "/assets/apple_icon.svg", "packaging_text": "Click below for instructions on how to submit to the Apple App Store.", "package_instructions": "https://docs.pwabuilder.com/#/builder/app-store" + }, + "Meta": + { + "logo": "/assets/meta_icon.svg", + "packaging_text": "Click below for instructions on how to submit to the Meta Horizon Store.", + "package_instructions": "https://developers.meta.com/horizon/documentation/web/pwa-packaging/" } } @@ -113,6 +123,17 @@ export class PublishPane extends LitElement { isActionCard: true, icon: '/assets/Publish_Apple.svg', renderDownloadButton: () => this.renderiOSDownloadButton() + }, + { + title: 'Meta Quest', + factoids: [ + "Bring your PWA to Meta Quest headsets", + "Distribute through the Meta Horizon Store", + "Supports immersive WebXR and 2D experiences" + ], + isActionCard: true, + icon: '/assets/Publish_Meta.svg', + renderDownloadButton: () => this.renderMetaDownloadButton() } ]; @@ -705,6 +726,14 @@ export class PublishPane extends LitElement { `; } + renderMetaDownloadButton(): TemplateResult { + return html` + + `; + } + renderForm() { if (this.selectedStore === "Windows") { return html`` @@ -729,7 +758,7 @@ export class PublishPane extends LitElement { html`` }` } else if (this.selectedStore === "Meta") { - return html` + return html` ` } else { return html`` @@ -813,6 +842,15 @@ export class PublishPane extends LitElement { return; } + // For Meta Horizon, queue up the packaging job and navigate to the Meta Horizon package status page. + if (options && platform === "meta") { + const metaHorizonPackageOptions = options as MetaHorizonPackageOptions; + metaHorizonPackageOptions.analysisId = this.analysisId || null; + const jobId = await enqueueMetaHorizonPackageJob(metaHorizonPackageOptions); + Router.go("/meta-horizon-packaging-status?jobId=" + encodeURIComponent(jobId)); + return; + } + const packageData = await generatePackage(platform, options); if (packageData) { @@ -916,6 +954,12 @@ export class PublishPane extends LitElement { stack_trace += (googlePlayError?.packageJob?.logs || []).join("\n"); message = (googlePlayError?.packageJob?.errors || []).join(", "); quick_desc = googlePlayError?.packageJob?.errors[0] || message || title; + } else if (this.selectedStore === "Meta") { + const metaHorizonError = err as MetaHorizonPackageError | null; + title = metaHorizonError?.message || "Error generating package"; + stack_trace += (metaHorizonError?.packageJob?.logs || []).join("\n"); + message = (metaHorizonError?.packageJob?.errors || []).join(", "); + quick_desc = metaHorizonError?.packageJob?.errors[0] || message || title; } else { title = response.statusText; stack_trace += err.stack; diff --git a/apps/pwabuilder/Frontend/src/script/models/meta-horizon-package-error.ts b/apps/pwabuilder/Frontend/src/script/models/meta-horizon-package-error.ts new file mode 100644 index 000000000..414c02d77 --- /dev/null +++ b/apps/pwabuilder/Frontend/src/script/models/meta-horizon-package-error.ts @@ -0,0 +1,9 @@ +import { MetaHorizonPackageJob } from "./meta-horizon-package-job"; + +export class MetaHorizonPackageError extends Error { + constructor(innerErrorOrMessage: unknown, public readonly packageJob: MetaHorizonPackageJob | null) { + const innerError = innerErrorOrMessage instanceof Error ? innerErrorOrMessage + : typeof innerErrorOrMessage === "string" ? new Error(innerErrorOrMessage) : new Error(`An unknown error occurred. ${innerErrorOrMessage || ""}`); + super(innerError.message, { ...innerError }); + } +} diff --git a/apps/pwabuilder/Frontend/src/script/models/meta-horizon-package-job.ts b/apps/pwabuilder/Frontend/src/script/models/meta-horizon-package-job.ts new file mode 100644 index 000000000..5d04a7629 --- /dev/null +++ b/apps/pwabuilder/Frontend/src/script/models/meta-horizon-package-job.ts @@ -0,0 +1,14 @@ +import { MetaHorizonPackageOptions } from "../utils/meta-horizon-validation"; + +export interface MetaHorizonPackageJob { + id: string; + pwaUrl: string; + analysisId: string | null; + status: "Queued" | "InProgress" | "Completed" | "Failed"; + createdAt: string; + retryCount: number; + errors: string[]; + logs: string[]; + packageOptions: MetaHorizonPackageOptions; + uploadedBlobFileName: string | null; +} diff --git a/apps/pwabuilder/Frontend/src/script/pages/app-home.ts b/apps/pwabuilder/Frontend/src/script/pages/app-home.ts index 4aacc7204..886407fb7 100644 --- a/apps/pwabuilder/Frontend/src/script/pages/app-home.ts +++ b/apps/pwabuilder/Frontend/src/script/pages/app-home.ts @@ -183,6 +183,7 @@ export class AppHome extends LitElement { Windows iOS Android + Meta Quest
diff --git a/apps/pwabuilder/Frontend/src/script/pages/app-report.ts b/apps/pwabuilder/Frontend/src/script/pages/app-report.ts index 512bb29cc..07b827d58 100644 --- a/apps/pwabuilder/Frontend/src/script/pages/app-report.ts +++ b/apps/pwabuilder/Frontend/src/script/pages/app-report.ts @@ -1143,6 +1143,7 @@ export class AppReport extends LitElement { Windows iOS Android + Meta Quest
diff --git a/apps/pwabuilder/Frontend/src/script/pages/meta-horizon-packaging-status.styles.ts b/apps/pwabuilder/Frontend/src/script/pages/meta-horizon-packaging-status.styles.ts new file mode 100644 index 000000000..865a868d8 --- /dev/null +++ b/apps/pwabuilder/Frontend/src/script/pages/meta-horizon-packaging-status.styles.ts @@ -0,0 +1,115 @@ +import { css } from "lit"; + +export const metaHorizonPackagingStatusStyles = css` + :host { + display: block; + background-image: url("/assets/new/Hero1920_withmani.webp"); + background-size: contain; + background-repeat: no-repeat; + background-position: center top; + + @media (max-width: 535px) { + background-position-y: 70px; + } + } + + .page-title { + display: flex; + align-items: center; + gap: 16px; + padding: 0 6px; + + sl-icon[name="check-circle-fill"] { + color: var(--sl-color-success-600); + } + + sl-icon[name="exclamation-octagon"] { + color: var(--sl-color-danger-600); + } + } + + .content { + max-width: 1300px; + margin-left: auto; + margin-right: auto; + margin-top: 100px; + padding: 16px; + + @media (max-width: 535px) { + padding: 0; + } + } + + sl-card { + width: 100%; + } + + .card-footer { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 16px; + } + + .pwa-header { + display: flex; + align-items: center; + gap: 16px; + overflow: clip; + + .pwa-icon { + max-width: 92px; + max-height: 92px; + height: auto; + } + + .pwa-title { + margin: 0; + } + + p { + margin: 0; + color: var(--sl-color-neutral-600); + font-size: 0.8em; + + @media (max-width: 535px) { + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 200px; + } + } + } + + .logs { + display: flex; + flex-direction: column; + gap: 4px; + background-color: var(--sl-color-neutral-200); + max-height: 400px; + height: 400px; + overflow: auto; + padding: 16px; + + .log { + display: block; + padding: 4px; + color: rgb(31, 35, 40); + font-size: 0.8em; + font-family: monospace; + + &:hover { + background-color: var(--sl-color-neutral-300); + } + + &.warn { + color: var(--sl-color-warning-600); + } + + &.error { + color: var(--sl-color-danger-600); + } + } + } +`; diff --git a/apps/pwabuilder/Frontend/src/script/pages/meta-horizon-packaging-status.ts b/apps/pwabuilder/Frontend/src/script/pages/meta-horizon-packaging-status.ts new file mode 100644 index 000000000..4d529c29d --- /dev/null +++ b/apps/pwabuilder/Frontend/src/script/pages/meta-horizon-packaging-status.ts @@ -0,0 +1,315 @@ +import { html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { customElement, property, state } from 'lit/decorators.js'; +import "../components/app-header"; +import { + downloadMetaHorizonPackageZip, + enqueueMetaHorizonPackageJob, + getMetaHorizonPackageJob, +} from "../services/publish/meta-horizon-publish"; +import { MetaHorizonPackageJob } from "../models/meta-horizon-package-job"; +import { env } from "../utils/environment"; +import "@shoelace-style/shoelace/dist/components/textarea/textarea"; +import { metaHorizonPackagingStatusStyles } from "./meta-horizon-packaging-status.styles"; +import { AnalyticsBehavior, recordProcessStep } from "@pwabuilder/site-analytics"; +import { repeat } from "lit/directives/repeat.js"; +import "@shoelace-style/shoelace/dist/components/card/card"; +import "@shoelace-style/shoelace/dist/components/button/button"; +import { Router } from "@vaadin/router"; +import "@shoelace-style/shoelace/dist/components/spinner/spinner"; +import "@shoelace-style/shoelace/dist/components/icon/icon"; + +/** + * A page that shows the status of a Meta Horizon packaging job. + */ +@customElement("meta-horizon-packaging-status") +export class MetaHorizonPackagingStatus extends LitElement { + @property({ attribute: "job-id" }) jobId: string | null = null; + @state() hasFailed = false; + @state() logs: string[] = []; + @state() job: MetaHorizonPackageJob | null = null; + @state() isRetrying = false; + private readonly pollIntervalMs = 3000; + private readonly maxWaitTimeMs = 15 * 60 * 1000; + private jobTimeoutHandle = 0; + + static styles = [metaHorizonPackagingStatusStyles]; + + connectedCallback(): void { + super.connectedCallback(); + + const search = new URLSearchParams(window.location.search.toLowerCase()); + const jobId = search.get("jobid"); + if (jobId) { + this.appendLog("Querying for job..."); + this.jobId = jobId; + } + } + + updated(changedProperties: PropertyValues) { + if (changedProperties.has("jobId") && this.jobId) { + const localJobId = this.jobId; + setTimeout(() => this.pollJob(localJobId), this.pollIntervalMs); + this.jobTimeoutHandle = window.setTimeout(() => this.jobTimedOut(), this.maxWaitTimeMs); + } + + if (changedProperties.has("logs")) { + this.logsScrollToBottom(); + } + } + + render(): TemplateResult { + return html` + +
+ ${this.renderTitle()} + + ${this.renderHeader()} +
+ ${repeat(this.logs, l => this.renderLog(l))} +
+ ${this.renderFooter()} +
+
+ `; + } + + renderTitle(): TemplateResult { + if (this.hasFailed) { + return html` +

+ + Unable to create Meta Horizon package +

+ `; + } + + if (!this.job) { + return html` +

+ + Checking Meta Horizon package status... +

+ `; + } + + if (this.job.status === "Queued") { + return html` +

+ + Waiting for agent to pick up job... +

+ `; + } + + if (this.job.status === "Failed") { + return html` +

+ + Unable to create Meta Horizon package +

+ `; + } + + if (this.job.status === "Completed") { + return html` +

+ + Package created successfully +

+ `; + } + + return html` +

+ + Creating your Meta Horizon package... +

+ `; + } + + renderHeader(): TemplateResult { + if (!this.job?.packageOptions) { + return html``; + } + + const queuedDate = new Date(this.job.createdAt); + const formattedDate = queuedDate.toLocaleString(); + return html` +
+ PWA Icon +
+

${this.job.packageOptions.name}

+

${this.job.packageOptions.webManifestUrl}

+

Queued for packaging at ${formattedDate}

+
+
+ `; + } + + renderFooter(): TemplateResult { + if (!this.job && !this.hasFailed) { + return html``; + } + + if (this.job?.status === "Failed" || this.hasFailed) { + const title = encodeURIComponent("Error creating Meta Horizon package"); + const lastErrorLog = this.getErrorLogForGitHubIssue(this.logs).replaceAll("\n", "\n> "); + const body = encodeURIComponent(`I received the [following error](https://pwabuilder.com/meta-horizon-packaging-status?jobId=${this.job?.id || this.jobId}) when creating a Meta Horizon package for ${this.job?.packageOptions.pwaUrl || "[empty]"}.\n\n> ${lastErrorLog}`); + return html` + + `; + } + + return html``; + } + + renderLog(log: string): TemplateResult { + let logClass = "log"; + if (log.includes("[error]")) { + logClass += " error"; + } + if (log.includes("[warn]")) { + logClass += " warn"; + } + + return html`${log}`; + } + + private async pollJob(jobId: string): Promise { + let job: MetaHorizonPackageJob; + try { + job = await getMetaHorizonPackageJob(jobId); + this.job = job; + } catch (error) { + this.pollJobFailed(jobId, error); + return; + } + + this.ensureLogContains(job.logs); + if (job.status === "Completed") { + await this.jobCompleted(job); + } else if (job.status === "Failed") { + this.jobFailed(job); + } else { + setTimeout(() => this.pollJob(jobId), this.pollIntervalMs); + } + } + + private async jobCompleted(job: MetaHorizonPackageJob): Promise { + const generatedDate = new Date(job.createdAt); + const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000); + if (generatedDate < oneDayAgo) { + this.appendLog(`Package was generated over 24 hours ago. Skipping download of package zip file. Old packages are automatically deleted after a period of time, but you may try downloading manually from ${env.metaHorizonPackageGeneratorUrl}/downloadPackageZip?id=${job.id}`); + clearTimeout(this.jobTimeoutHandle); + return; + } + + let blob: Blob; + try { + blob = await downloadMetaHorizonPackageZip(job.id); + clearTimeout(this.jobTimeoutHandle); + this.downloadBlob(blob, job); + this.appendLog("Package created successfully. Download has begun."); + } catch (downloadError) { + this.downloadFailed(job, downloadError); + } + } + + private jobTimedOut(): void { + this.appendLog("[error] Timed out waiting for Meta Horizon packaging job to complete."); + this.hasFailed = true; + } + + private pollJobFailed(jobId: string, error: unknown): void { + this.appendLog(`[error] Error when querying for Meta Horizon packaging job ${jobId}.`); + this.hasFailed = true; + this.trackPackageFailure(error); + } + + private jobFailed(job: MetaHorizonPackageJob): void { + this.hasFailed = true; + console.error("Meta Horizon packaging job failed.", job.errors); + this.trackPackageFailure(job.errors.join("\n")); + } + + private downloadFailed(job: MetaHorizonPackageJob, error: any): void { + const downloadUrl = `${env.metaHorizonPackageGeneratorUrl}/downloadPackageZip?id=${encodeURIComponent(job.id)}`; + this.appendLog(`Error downloading Meta Horizon package from ${downloadUrl} for job ${job.id}: ${error}`); + this.hasFailed = true; + this.trackPackageFailure(error); + } + + private appendLog(message: string): void { + this.logs = [...this.logs, message]; + } + + private ensureLogContains(logs: string[]): void { + logs + .filter(l => !this.logs.includes(l)) + .forEach(l => this.appendLog(l)); + } + + private downloadBlob(blob: Blob, job: MetaHorizonPackageJob): void { + const url = window.URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `${job.packageOptions.launcherName || job.packageOptions.name || "My PWA"} - Meta Horizon package.zip`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + } + + private trackPackageFailure(error: unknown): void { + recordProcessStep("analyze-and-package-pwa", + `create-meta-horizon-package-failed`, + AnalyticsBehavior.CancelProcess, + { + url: this.job?.packageOptions?.pwaUrl || "", + error: error + }); + recordProcessStep( + "pwa-builder", + `create-meta-horizon-package-failed`, + AnalyticsBehavior.CancelProcess, + { + url: this.job?.packageOptions?.pwaUrl || "", + error: error + }); + } + + private logsScrollToBottom(): void { + const logsElement = this.shadowRoot?.querySelector(".logs"); + if (logsElement) { + logsElement.scrollTo({ + top: logsElement.scrollHeight, + behavior: "smooth" + }); + } + } + + private async retryJob(): Promise { + if (!this.job?.packageOptions) { + console.error("Can't retry job because the job or its package options are missing."); + return; + } + + this.isRetrying = true; + try { + const newJobId = await enqueueMetaHorizonPackageJob(this.job.packageOptions); + Router.go(`/meta-horizon-packaging-status?jobId=${newJobId}`); + } catch (error) { + this.appendLog("Error retrying job: " + error); + } + } + + private getErrorLogForGitHubIssue(logs: string[]): string { + const logsReversed = [...logs].reverse(); + const errorLogs = logsReversed.filter(l => l.includes("[error]")); + const logWithStack = errorLogs.find(l => l.includes("\n")); + return logWithStack || errorLogs[0] || "No logs available"; + } +} diff --git a/apps/pwabuilder/Frontend/src/script/services/publish/index.ts b/apps/pwabuilder/Frontend/src/script/services/publish/index.ts index 1cf624856..14cd44658 100644 --- a/apps/pwabuilder/Frontend/src/script/services/publish/index.ts +++ b/apps/pwabuilder/Frontend/src/script/services/publish/index.ts @@ -1,15 +1,23 @@ import { GooglePlayPackageError } from "../../models/google-play-package-error"; import { GooglePlayPackageJob } from "../../models/google-play-package-job"; +import { MetaHorizonPackageError } from "../../models/meta-horizon-package-error"; +import { MetaHorizonPackageJob } from "../../models/meta-horizon-package-job"; import { AndroidPackageOptions } from '../../utils/android-validation'; import { PackageOptions } from '../../utils/interfaces'; import { IOSAppPackageOptions } from '../../utils/ios-validation'; import { WindowsPackageOptions } from '../../utils/win-validation'; +import { MetaHorizonPackageOptions } from '../../utils/meta-horizon-validation'; import { downloadGooglePlayPackageZip, enqueueGooglePlayPackageJob, getGooglePlayPackageJob, } from './android-publish'; import { generateIOSPackage } from './ios-publish'; +import { + downloadMetaHorizonPackageZip, + enqueueMetaHorizonPackageJob, + getMetaHorizonPackageJob, +} from './meta-horizon-publish'; import { generateWindowsPackage, } from './windows-publish'; @@ -34,6 +42,8 @@ export async function generatePackage( return await tryGenerateAndroidPackage(packageOptions as AndroidPackageOptions); case 'ios': return await tryGenerateIOSPackage(packageOptions as IOSAppPackageOptions); + case 'meta': + return await tryGenerateMetaHorizonPackage(packageOptions as MetaHorizonPackageOptions); default: throw new Error( `A platform type must be passed, ${type} is not a valid platform.` @@ -41,6 +51,10 @@ export async function generatePackage( } } +function tryGenerateMetaHorizonPackage(options: MetaHorizonPackageOptions): Promise { + return new MetaHorizonPackageService(options).createPackage(); +} + async function tryGenerateIOSPackage(options: IOSAppPackageOptions): Promise { const result = await generateIOSPackage(options); return { @@ -134,3 +148,67 @@ class GooglePlayPackageService { this.promiseWithResolvers.reject(new GooglePlayPackageError("Timed out waiting for Google Play package job to complete.", this.job)); } } + +/** + * Service for enqueuing a Meta Horizon package job, monitoring its status, and retrieving the resulting package. + */ +class MetaHorizonPackageService { + private readonly pollIntervalMs = 3000; // Poll the job every 3 seconds + private readonly maxWaitTimeMs = 15 * 60 * 1000; // Max wait time of 15 minutes + private readonly promiseWithResolvers = Promise.withResolvers(); + private job: MetaHorizonPackageJob | null = null; + private jobTimeoutHandle = 0; + + constructor(private readonly packageOptions: MetaHorizonPackageOptions) { + } + + public async createPackage(): Promise { + let jobId: string; + try { + jobId = await enqueueMetaHorizonPackageJob(this.packageOptions); + } catch (enqueueError) { + return Promise.reject(new MetaHorizonPackageError(enqueueError, null)); + } + + setTimeout(() => this.pollJob(jobId), this.pollIntervalMs); + this.jobTimeoutHandle = window.setTimeout(() => this.jobTimedOut(), this.maxWaitTimeMs); + return this.promiseWithResolvers.promise; + } + + private async pollJob(jobId: string): Promise { + try { + const job = await getMetaHorizonPackageJob(jobId); + this.job = job; + if (job.status === "Completed") { + await this.jobCompleted(job); + } else if (job.status === "Failed") { + this.promiseWithResolvers.reject(new MetaHorizonPackageError("Meta Horizon package job failed.", job)); + } else { + setTimeout(() => this.pollJob(jobId), this.pollIntervalMs); + } + } catch (error) { + console.error("Failed to retrieve Meta Horizon package job", error); + this.promiseWithResolvers.reject(new MetaHorizonPackageError(error, null)); + } + } + + private async jobCompleted(job: MetaHorizonPackageJob): Promise { + let blob: Blob; + try { + blob = await downloadMetaHorizonPackageZip(job.id); + this.promiseWithResolvers.resolve({ + appName: this.packageOptions.name, + blob: blob, + type: "store" + }); + clearTimeout(this.jobTimeoutHandle); + } catch (downloadError) { + console.error("Failed to download Meta Horizon package zip file", downloadError); + this.promiseWithResolvers.reject(new MetaHorizonPackageError(downloadError, job)); + } + } + + private jobTimedOut(): void { + this.promiseWithResolvers.reject(new MetaHorizonPackageError("Timed out waiting for Meta Horizon package job to complete.", this.job)); + } +} diff --git a/apps/pwabuilder/Frontend/src/script/services/publish/meta-horizon-publish.ts b/apps/pwabuilder/Frontend/src/script/services/publish/meta-horizon-publish.ts new file mode 100644 index 000000000..1f79b4a3e --- /dev/null +++ b/apps/pwabuilder/Frontend/src/script/services/publish/meta-horizon-publish.ts @@ -0,0 +1,251 @@ +import { MetaHorizonPackageJob } from '../../models/meta-horizon-package-job'; +import { generatePackageId, sanitizeDname } from '../../utils/android-validation'; +import { env } from '../../utils/environment'; +import { findSuitableIcon, findBestAppIcon } from '../../utils/icons'; +import { ManifestContext } from '../../utils/interfaces'; +import { + MetaHorizonPackageOptions, + validateMetaHorizonOptions, +} from '../../utils/meta-horizon-validation'; +import { getHeaders } from '../../utils/platformTrackingHeaders'; + +export let hasGeneratedMetaHorizonPackage = false; + +export async function enqueueMetaHorizonPackageJob( + options: MetaHorizonPackageOptions +): Promise { + const validationErrors = validateMetaHorizonOptions(options); + if (validationErrors.length > 0 || !options) { + throw new Error( + 'Invalid Meta Horizon options. ' + validationErrors.map(a => a.error).join('\n') + ); + } + + const headers = { ...getHeaders(), 'content-type': 'application/json' }; + const referrer = sessionStorage.getItem('ref'); + const url = `${env.metaHorizonPackageGeneratorUrl}/enqueuePackageJob${referrer ? '?ref=' + encodeURIComponent(referrer) : ''}`; + const response = await fetch(url, { + method: 'POST', + body: JSON.stringify(options), + headers: new Headers(headers), + }); + + if (response.ok) { + hasGeneratedMetaHorizonPackage = true; + return await response.text(); // job ID + } + + const err = new Error( + `Error enqueueing Meta Horizon package job.\nStatus code: ${response.status}\nError: ${response.statusText}` + ); + //@ts-ignore + err.response = response; + throw err; +} + +export async function getMetaHorizonPackageJob(jobId: string): Promise { + const jobFetch = await fetch( + `${env.metaHorizonPackageGeneratorUrl}/getPackageJob?id=${encodeURIComponent(jobId)}` + ); + if (!jobFetch.ok) { + throw new Error(`Error fetching Meta Horizon package job status: ${jobFetch.statusText}`); + } + return await jobFetch.json(); +} + +export async function downloadMetaHorizonPackageZip(jobId: string): Promise { + const zipFetch = await fetch( + `${env.metaHorizonPackageGeneratorUrl}/downloadPackageZip?id=${encodeURIComponent(jobId)}` + ); + if (!zipFetch.ok) { + throw new Error(`Error downloading Meta Horizon package ZIP: ${zipFetch.statusText}`); + } + return await zipFetch.blob(); +} + +export function emptyMetaHorizonPackageOptions(): MetaHorizonPackageOptions { + return { + appVersion: '1.0.0.0', + appVersionCode: 1, + backgroundColor: '#ffffff', + display: 'standalone', + enableNotifications: false, + enableMicrophone: false, + enableXRScene: false, + enableSiteSettingsShortcut: true, + fallbackType: 'customtabs', + features: { + locationDelegation: { enabled: false }, + playBilling: { enabled: false }, + horizonBilling: { enabled: false, horizonOSAppMode: 'immersive' }, + horizonPlatformSDK: { enabled: false }, + arCore: { enabled: false }, + }, + host: '', + horizonOSAppMode: 'immersive', + metaHorizonAppId: '', + iconUrl: '', + includeSourceCode: false, + launcherName: '', + maskableIconUrl: '', + monochromeIconUrl: '', + name: '', + navigationColor: '#ffffff', + navigationColorDark: '#000000', + navigationDividerColor: '#ffffff', + navigationDividerColorDark: '#000000', + orientation: 'default', + packageId: '', + pwaUrl: '', + shortcuts: [], + signing: { + file: null, + alias: '', + fullName: '', + organization: '', + organizationalUnit: '', + countryCode: '', + keyPassword: '', + storePassword: '', + }, + signingMode: 'none', + splashScreenFadeOutDuration: 300, + startUrl: '', + themeColor: '#ffffff', + themeColorDark: '#000000', + webManifestUrl: '', + fullScopeUrl: '', + minSdkVersion: 23, + }; +} + +export function createMetaHorizonPackageOptionsFromManifest( + manifestContext: ManifestContext +): MetaHorizonPackageOptions { + const maniUrl = manifestContext.manifestUrl; + const pwaUrl = manifestContext.siteUrl; + const manifest = manifestContext.manifest; + + if (!pwaUrl) { + throw new Error("Can't find the current URL"); + } + if (!maniUrl) { + throw new Error('Cant find the manifest URL'); + } + + const appName = manifest.short_name || manifest.name || 'My PWA'; + const packageName = generatePackageId(new URL(pwaUrl).hostname); + const display = manifest.display === 'fullscreen' ? 'fullscreen' : 'standalone'; + + const manifestIcons = manifest.icons || []; + const icon = findBestAppIcon(manifestIcons); + if (!icon) { + throw new Error( + "Can't find a suitable icon to use for the Meta Horizon package. Ensure your manifest has a square, large (512x512 or better) PNG icon." + ); + } + + const maskableIcon = + findSuitableIcon(manifestIcons, 'maskable', 512, 512, 'image/png') || + findSuitableIcon(manifestIcons, 'maskable', 192, 192, 'image/png') || + findSuitableIcon(manifestIcons, 'maskable', 192, 192, undefined); + const monochromeIcon = + findSuitableIcon(manifestIcons, 'monochrome', 512, 512, 'image/png') || + findSuitableIcon(manifestIcons, 'monochrome', 192, 192, 'image/png') || + findSuitableIcon(manifestIcons, 'monochrome', 192, 192, undefined); + const navColorOrFallback = manifest.theme_color || manifest.background_color || '#000000'; + + const manifestUrlOrRoot = maniUrl.startsWith('data:application/manifest+json,') + ? pwaUrl + : maniUrl; + + const fullScopeUrl = new URL(manifest.scope || '.', manifestUrlOrRoot).toString(); + + const shareTarget = manifest.share_target; + if (shareTarget) { + const toParseFiles = shareTarget.params?.files; + if (toParseFiles) { + for (const files of toParseFiles) { + if (files.accept) { + const goodFiles: string[] = []; + files.accept.forEach(accept => { + if (!accept.startsWith('.')) { + goodFiles.push(accept); + } + }); + files.accept = goodFiles; + } + } + } + } + + return { + appVersion: '1.0.0.0', + appVersionCode: 1, + backgroundColor: manifest.background_color || manifest.theme_color || '#FFFFFF', + display: display, + enableNotifications: true, + enableMicrophone: false, + enableXRScene: false, + enableSiteSettingsShortcut: true, + fallbackType: 'customtabs', + features: { + locationDelegation: { enabled: false }, + playBilling: { enabled: false }, + horizonBilling: { enabled: false, horizonOSAppMode: 'immersive' }, + horizonPlatformSDK: { enabled: false }, + arCore: { enabled: false }, + }, + host: new URL(pwaUrl).host, + horizonOSAppMode: 'immersive', + metaHorizonAppId: '', + iconUrl: getAbsoluteUrl(icon.src, manifestUrlOrRoot), + includeSourceCode: false, + launcherName: (manifest.short_name || appName || 'My PWA').substring(0, 30), + maskableIconUrl: getAbsoluteUrl(maskableIcon?.src, manifestUrlOrRoot), + monochromeIconUrl: getAbsoluteUrl(monochromeIcon?.src, manifestUrlOrRoot), + name: appName, + navigationColor: navColorOrFallback, + navigationColorDark: navColorOrFallback, + navigationDividerColor: navColorOrFallback, + navigationDividerColorDark: navColorOrFallback, + orientation: manifest.orientation || 'default', + packageId: packageName, + shortcuts: manifest.shortcuts || [], + signing: { + file: null, + alias: 'my-key-alias', + fullName: `${sanitizeDname(manifest.short_name) || sanitizeDname(manifest.name) || 'App'} Admin`, + organization: sanitizeDname(manifest.name) || 'PWABuilder', + organizationalUnit: 'Engineering', + countryCode: 'US', + keyPassword: '', + storePassword: '', + }, + signingMode: 'new', + splashScreenFadeOutDuration: 300, + startUrl: getStartUrlRelativeToHost(manifest.start_url, new URL(manifestUrlOrRoot)), + themeColor: manifest.theme_color || '#FFFFFF', + themeColorDark: manifest.theme_color || '#000000', + shareTarget: manifest.share_target, + webManifestUrl: maniUrl, + pwaUrl: manifestContext.siteUrl, + fullScopeUrl: fullScopeUrl, + minSdkVersion: 23, + }; +} + +function getAbsoluteUrl(relativeUrl: string | undefined, manifestUrl: string): string { + if (!relativeUrl) { + return ''; + } + return new URL(relativeUrl, manifestUrl).toString(); +} + +function getStartUrlRelativeToHost( + startUrl: string | null | undefined, + manifestUrl: URL | string +) { + const absoluteStartUrl = new URL(startUrl || '/', manifestUrl); + return absoluteStartUrl.pathname + (absoluteStartUrl.search || ''); +} diff --git a/apps/pwabuilder/Frontend/src/script/utils/environment.ts b/apps/pwabuilder/Frontend/src/script/utils/environment.ts index 6bd410e8d..481cfc6d5 100644 --- a/apps/pwabuilder/Frontend/src/script/utils/environment.ts +++ b/apps/pwabuilder/Frontend/src/script/utils/environment.ts @@ -5,6 +5,7 @@ export const env = { api: '', windowsPackageGeneratorUrl: '', androidPackageGeneratorUrl: '', + metaHorizonPackageGeneratorUrl: '', iosPackageGeneratorUrl: '', safeUrlFetcher: '', validateGiveawayUrl: '', @@ -22,6 +23,8 @@ if (import.meta.env.PROD) { 'https://pwabuilder-windows-docker.azurewebsites.net/msix/generatezip'; env.androidPackageGeneratorUrl = 'https://pwabuilder-cloudapk.azurewebsites.net'; + env.metaHorizonPackageGeneratorUrl = + 'https://pwabuilder-cloudmetahorizon.azurewebsites.net'; env.iosPackageGeneratorUrl = '/api/iospackage/create'; env.safeUrlFetcher = '/api/images/getSafeImageForAnalysis'; @@ -36,6 +39,8 @@ if (import.meta.env.PROD) { 'https://localhost:5001/msix/generatezip'; env.androidPackageGeneratorUrl = 'http://localhost:5858'; + env.metaHorizonPackageGeneratorUrl = + 'http://localhost:5859'; env.iosPackageGeneratorUrl = '/api/iospackage/create'; env.safeUrlFetcher = '/api/images/getSafeImageForAnalysis'; diff --git a/apps/pwabuilder/Frontend/src/script/utils/meta-horizon-validation.ts b/apps/pwabuilder/Frontend/src/script/utils/meta-horizon-validation.ts new file mode 100644 index 000000000..0f32a5eb6 --- /dev/null +++ b/apps/pwabuilder/Frontend/src/script/utils/meta-horizon-validation.ts @@ -0,0 +1,259 @@ +import { PackageOptions, ShareTarget, ShortcutItem } from './interfaces'; +import { validateAndroidPackageId, validateUrl, AndroidSigningOptions } from './android-validation'; + +/** + * Settings for Meta Horizon Store package generation. Mirrors the backend + * MetaHorizonPackageOptions interface in apps/pwabuilder-meta-horizon/models/metaHorizonPackageOptions.ts. + */ +export interface MetaHorizonPackageOptions extends PackageOptions { + analysisId?: string | null; + additionalTrustedOrigins?: string[]; + appVersion: string; + appVersionCode: number; + backgroundColor: string; + display: 'standalone' | 'fullscreen' | 'fullscreen-sticky'; + enableNotifications: boolean; + enableMicrophone?: boolean; + enableXRScene?: boolean; + enableSiteSettingsShortcut?: boolean; + fallbackType: 'customtabs' | 'webview'; + features: { + appsFlyer?: { enabled: boolean; appsFlyerId: string }; + locationDelegation?: { enabled: boolean }; + playBilling?: { enabled: boolean }; + horizonBilling?: { enabled: boolean; horizonOSAppMode?: '2D' | 'immersive' }; + horizonPlatformSDK?: { enabled: boolean }; + firstRunFlag?: { enabled: boolean; queryParameterName: string }; + arCore?: { enabled: boolean }; + }; + host: string; + horizonOSAppMode?: '2D' | 'immersive'; + metaHorizonAppId?: string; + iconUrl: string; + includeSourceCode: boolean; + launcherName: string; + maskableIconUrl?: string | null; + monochromeIconUrl?: string | null; + name: string; + navigationColor: string; + navigationColorDark?: string; + navigationDividerColor?: string; + navigationDividerColorDark?: string; + orientation?: + | 'default' + | 'any' + | 'natural' + | 'landscape' + | 'portrait' + | 'portrait-primary' + | 'portrait-secondary' + | 'landscape-primary' + | 'landscape-secondary'; + packageId: string; + pwaUrl: string; + shareTarget?: ShareTarget; + shortcuts?: ShortcutItem[]; + signing?: AndroidSigningOptions | null; + signingMode: 'new' | 'none' | 'mine'; + splashScreenFadeOutDuration: number; + startUrl: string; + themeColor: string; + themeColorDark?: string; + webManifestUrl: string; + fullScopeUrl: string; + minSdkVersion?: number; +} + +export type MetaHorizonPackageValidationError = { + field: keyof MetaHorizonPackageOptions | keyof AndroidSigningOptions | null; + error: string; +}; + +export function validateMetaHorizonOptions( + options: Partial | null | undefined +): MetaHorizonPackageValidationError[] { + const errors: MetaHorizonPackageValidationError[] = []; + if (!options) { + errors.push({ field: null, error: 'No options specified' }); + return errors; + } + + // Package ID rules are the same as Android. + validateAndroidPackageId(options.packageId).forEach(e => + errors.push({ field: e.field as keyof MetaHorizonPackageOptions, error: e.error }) + ); + + if (!options.name || options.name.trim().length === 0) { + errors.push({ field: 'name', error: 'Must have a valid app name' }); + } else if (options.name.length < 3 || options.name.length > 50) { + errors.push({ field: 'name', error: 'Name must be between 3 and 50 characters in length' }); + } + + if (options.launcherName && (options.launcherName.length < 3 || options.launcherName.length > 30)) { + errors.push({ + field: 'launcherName', + error: 'Launcher name must be between 3 and 30 characters in length', + }); + } + + if (!options.appVersion || options.appVersion.trim().length === 0) { + errors.push({ field: 'appVersion', error: 'Must have a valid app version' }); + } + + if (!options.appVersionCode || options.appVersionCode > 2100000000) { + errors.push({ + field: 'appVersionCode', + error: 'App version code must be between 1 and 2,100,000,000', + }); + } + + if (!options.backgroundColor) { + errors.push({ field: 'backgroundColor', error: 'Must have a background color' }); + } + + if ( + options.display !== 'standalone' && + options.display !== 'fullscreen' && + options.display !== 'fullscreen-sticky' + ) { + errors.push({ + field: 'display', + error: "Display must be 'standalone', 'fullscreen', or 'fullscreen-sticky'", + }); + } + + if (options.fallbackType !== 'customtabs' && options.fallbackType !== 'webview') { + errors.push({ + field: 'fallbackType', + error: "Fallback type must be 'customtabs' or 'webview'", + }); + } + + if (!options.host) { + errors.push({ field: 'host', error: 'Host must be specified' }); + } else { + const hostWithProtocol = options.host.startsWith('https') ? options.host : `https://${options.host}`; + if (validateUrl(hostWithProtocol)) { + errors.push({ field: 'host', error: 'Host URL must be a valid URL' }); + } + } + + if (!options.iconUrl) { + errors.push({ field: 'iconUrl', error: 'Must have an icon URL' }); + } else if (validateUrl(options.iconUrl, options.webManifestUrl)) { + errors.push({ field: 'iconUrl', error: 'Icon URL is invalid' }); + } + + if (!options.launcherName || options.launcherName.trim().length === 0) { + errors.push({ field: 'launcherName', error: 'Must have an app launcher name' }); + } + + if (!options.webManifestUrl) { + errors.push({ field: 'webManifestUrl', error: 'Must have a manifest URL' }); + } else { + if (validateUrl(options.webManifestUrl)) { + errors.push({ field: 'webManifestUrl', error: 'Manifest URL is invalid' }); + } else if (!options.webManifestUrl.startsWith('https://')) { + errors.push({ field: 'webManifestUrl', error: 'Manifest URL must be an absolute HTTPS URL' }); + } + } + + if (options.maskableIconUrl && validateUrl(options.maskableIconUrl, options.webManifestUrl)) { + errors.push({ field: 'maskableIconUrl', error: 'Maskable icon URL is invalid' }); + } + + if (options.monochromeIconUrl && validateUrl(options.monochromeIconUrl, options.webManifestUrl)) { + errors.push({ field: 'monochromeIconUrl', error: 'Monochrome icon URL is invalid' }); + } + + if (!options.navigationColor) { + errors.push({ field: 'navigationColor', error: 'Navigation color is required' }); + } + + if (!options.fullScopeUrl) { + errors.push({ field: 'fullScopeUrl', error: 'Full scope URL is required for Meta Horizon' }); + } else if (validateUrl(options.fullScopeUrl)) { + errors.push({ field: 'fullScopeUrl', error: 'Full scope URL must be a valid URL' }); + } + + // Horizon-specific: app mode must be one of the supported values when set. + if (options.horizonOSAppMode && options.horizonOSAppMode !== '2D' && options.horizonOSAppMode !== 'immersive') { + errors.push({ + field: 'horizonOSAppMode', + error: "Horizon OS app mode must be either '2D' or 'immersive'", + }); + } + + // metaHorizonAppId must be numeric when set, and required when Horizon Billing is enabled. + if (options.metaHorizonAppId && !/^\d+$/.test(options.metaHorizonAppId)) { + errors.push({ + field: 'metaHorizonAppId', + error: 'Meta Horizon app ID must be a numeric string from the Meta Developer Dashboard.', + }); + } + if (options.features?.horizonBilling?.enabled && !options.metaHorizonAppId) { + errors.push({ + field: 'metaHorizonAppId', + error: 'Meta Horizon app ID is required when Horizon Billing is enabled.', + }); + } + + if (options.signingMode === 'mine' || options.signingMode === 'new') { + if (!options.signing) { + errors.push({ field: 'signing', error: 'Signing information must be supplied.' }); + } else { + const requiredSigningFields: Array = ['alias']; + if (options.signingMode === 'mine') { + requiredSigningFields.push('file', 'keyPassword', 'storePassword'); + } else if (options.signingMode === 'new') { + requiredSigningFields.push('fullName', 'organization', 'organizationalUnit', 'countryCode'); + } + requiredSigningFields + .filter(prop => !options.signing![prop]) + .forEach(prop => + errors.push({ field: prop, error: `${prop.toString()} must be specified` }) + ); + + if (options.signing.keyPassword && options.signing.keyPassword.length < 6) { + errors.push({ field: 'keyPassword', error: 'Key password must be at least 6 characters' }); + } + if (options.signing.storePassword && options.signing.storePassword.length < 6) { + errors.push({ field: 'storePassword', error: 'Key store password must be at least 6 characters' }); + } + if ( + options.signingMode === 'new' && + options.signing.countryCode && + options.signing.countryCode.length !== 2 + ) { + errors.push({ field: 'countryCode', error: 'Signing key country code must be 2 letters' }); + } + } + } + + if ( + options.splashScreenFadeOutDuration === null || + options.splashScreenFadeOutDuration === undefined || + options.splashScreenFadeOutDuration < 0 + ) { + errors.push({ + field: 'splashScreenFadeOutDuration', + error: 'Splash screen fade duration must be 0 or greater', + }); + } + + if (!options.startUrl) { + errors.push({ + field: 'startUrl', + error: + "Start URL must be specified. If your start URL is the same as Host, you can use '/' as the start URL.", + }); + } else if (validateUrl(options.startUrl, options.webManifestUrl)) { + errors.push({ field: 'startUrl', error: 'Start URL is invalid' }); + } + + if (!options.themeColor) { + errors.push({ field: 'themeColor', error: 'Theme color must be specified' }); + } + + return errors; +}