diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..772bfbbb9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +src/**/firebase.ts linguist-generated=true +src/**/rxfire.ts linguist-generated=true +src/compat/**/base.ts linguist-generated=true +samples/**/* linguist-generated=true +yarn.lock linguist-generated=true \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..575a25a3f --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,73 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ main ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ main ] + schedule: + - cron: '20 23 * * 3' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..f52ae128e --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,184 @@ +name: Test and publish + +on: + push: + branches: + - main + paths-ignore: + - "**/*.md" + pull_request: + branches: + - "**" + release: + types: + - published + schedule: + - cron: 0 0 * * 1-5 + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + name: Build + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + - name: Setup node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '20' + check-latest: false + - name: Install deps + run: npm ci + - name: Build + run: ./tools/build.sh + - name: 'Upload Artifact' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: angularfire-${{ github.run_id }} + path: dist + retention-days: 1 + + test: + runs-on: ${{ matrix.os }} + needs: build + strategy: + matrix: + os: [ ubuntu-latest, macos-latest, windows-latest ] + node: ["20", "22", "24"] + fail-fast: false + name: Test Node ${{ matrix.node }} (${{ matrix.os }}) + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + - name: Setup node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: ${{ matrix.node }} + check-latest: true + - name: Install deps + run: npm ci + - name: Download Artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: angularfire-${{ github.run_id }} + path: dist + - name: Test Node + run: | + npm run build:jasmine + npm run test:node + + browser: + runs-on: ${{ matrix.os }} + needs: build + name: Test ${{ matrix.browser }} + strategy: + matrix: + os: [ ubuntu-latest ] + browser: [ chrome-headless, firefox-headless ] + # TODO(davideast): Figure out why Safari tests timeout only on CI + # include: + # - os: macos-latest + # browser: safari + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + - name: Setup node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 20 + check-latest: false + - name: Setup java + uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0 + with: + distribution: 'temurin' + java-version: '21' + - name: Install deps + run: npm ci + - name: Download Artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: angularfire-${{ github.run_id }} + path: dist + - name: Test browser + run: npm run test:${{ matrix.browser }} + + contribute: + runs-on: ${{ matrix.os }} + name: Contribute Node ${{ matrix.node }} (${{ matrix.os }}) + strategy: + matrix: + os: [ ubuntu-latest, macos-latest, windows-latest ] + node: ["20"] + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + - name: Setup node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: ${{ matrix.node }} + check-latest: true + - name: Install deps + run: npm ci + #- name: Lint + # run: npm run lint + - name: Build + run: npm run build + - name: Test Node + run: | + npm run build:jasmine + npm run test:node + - name: Setup java + uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0 + with: + distribution: 'temurin' + java-version: '21' + - name: Test headless + run: npm run test:chrome-headless + # Tests are flaky on Windows + continue-on-error: ${{ matrix.os == 'windows-latest' }} + + # Break the branch protection test into a seperate step, so we can manage the matrix more easily + test_and_contribute: + runs-on: ubuntu-latest + name: Branch protection + needs: ['test', 'contribute', 'browser'] + steps: + - run: true + + publish: + runs-on: ubuntu-latest + name: Publish (NPM) + needs: ['build', 'test', 'browser'] + if: ${{ github.ref == 'refs/heads/main' || github.event_name == 'release' }} + steps: + - name: Setup node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '20' + registry-url: 'https://wombat-dressing-room.appspot.com/' + check-latest: false + - name: 'Download Artifacts' + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: angularfire-${{ github.run_id }} + path: dist + - name: Publish + run: | + cd ./dist/packages-dist + chmod +x publish.sh + ./publish.sh + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index 6ba88e35b..beb5d455e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,9 +13,15 @@ angularfire2-*.tgz .DS_Store yarn-error.log *.bak -package-lock.json +yarn.lock test/ng-build/**/yarn.lock tools/build.js coverage *.log -api-*.json \ No newline at end of file +api-*.json +angularfire.tgz +unpack.sh +publish.sh +.firebase +.angular +.vscode \ No newline at end of file diff --git a/.npmignore b/.npmignore index 87f7a19b5..02a57a315 100644 --- a/.npmignore +++ b/.npmignore @@ -1,2 +1,7 @@ *.spec.* test-config.* +publish.sh +__ivy_ngcc__/ +*.min.js +*.min.js.map +*.__ivy_ngcc_bak \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 229664fbd..000000000 --- a/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -language: node_js -sudo: false -node_js: 10 - -addons: - chrome: stable - -matrix: - fast_finish: true - allow_failures: - - env: CANARY=true - -branches: - only: master # otherwise pull requests get built twice - -install: yarn - -script: yarn build && yarn test:all && yarn lint diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index ef264db9c..000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,653 +0,0 @@ - -# [6.0.2](https://github.com/angular/angularfire2/compare/6.0.1...6.0.2) (2020-06-24) - -* Quick fix to peers so `ng add @angular/fire` installs the correct version on Angular 10 - - -# [6.0.1](https://github.com/angular/angularfire2/compare/6.0.0...6.0.1) (2020-06-24) - -* Updating peer dependencies to allow for Angular 10 -* `ng add @angular/fire` should correctly add the `firebase` peer -* `ng add @angular/fire` will not duplicate settings entries, if they're already present -* `ng add @angular/fire` will error if there are peer incompatabilities -* `ng deploy` should function correctly on Windows devices -* `ng deploy` will now mark the Angular assets as immutable on Firebase Hosting -* RTDB and Firestore CRUD operations should return in the ngZone -* Use of `AngularFireAuthGuard` should no longer destablize Zone.js - - -# [6.0.0](https://github.com/angular/angularfire2/compare/6.0.0-rc.2...6.0.0) (2020-04-01) - -Final relase of version 6. - -* Updating peer dependencies -* `ng add @angular/fire` now will overwrite firebase config, if present -* `ng add @angular/fire` now adds `@firebase/firestore` to the server schematic `externalDependencies` if present -* `ng deploy --preview` is now interactive and functions on non-SSR -* `ng deploy` will respect the `bundleDependencies` and `externalDependencies` server schematic options -* `ng deploy` now defaults to 1GB of ram on Cloud Functions -* Fixed various issues with functions deploy -* Simplified `AngularPerformanceMonitoring` - - -# [6.0.0-rc.2](https://github.com/angular/angularfire2/compare/6.0.0-rc.1...6.0.0-rc.2) (2020-03-29) - -Continued work on 6.0. - -* Update peers -* No longer require developer add bare imports themselves (e.g, `import 'firebase/firestore'`) -* Zone.js fixes -* Angular Universal deploy schematic -* Storage listAll() - - -# [6.0.0-rc.1](https://github.com/angular/angularfire2/compare/6.0.0-rc.0...6.0.0-rc.1) (2020-02-06) - -Continued work on version 6. - -* Brought a fix in `5.4.2` ([#2315](https://github.com/angular/angularfire2/issues/2315)) -* Fixed `@angular/fire/analytics` attempting to use `global` ([#2303](https://github.com/angular/angularfire/issues/2303)) -* Fix the error message on storage ([#2313](https://github.com/angular/angularfire/issues/2313)) -* Starting on documentation for 6.0 - - -# [6.0.0-rc.0](https://github.com/angular/angularfire2/compare/5.4.1...6.0.0-rc.0) (2020-01-30) - -Version 6 of AngularFire drops support for Angular version 8 and below, older versions of typescript, Firebase, drops `firebase-node`, `database-deprecated`, and more. - -* Support for Angular versions less than 9 has been dropped -* Support for Firebase versions less than 7.8 has been dropped -* Support for `firebase-tools` less than 7.12 has been dropped -* `angularfire2` NPM shim will no longer be updated -* Dropped `@angular/fire/firebase-node` and `@angular/fire/database-depreciated` -* Using `ng-packagr` to build the library, bringing us back up to speed on APF -* All of our `@NgModules` are now `providedIn: 'any'` rather than singletons -* We make use of Proxy in more modules, you'll need to polyfill if you want to support IE 11 - -#### `@angular/fire` - -* Dropped the `RealtimeDatabaseURL` and `DATABASE_URL` DI tokens, use `import { URL } from '@angular/fire/database'` instead -* Dropped `runOutsideAngular`, `runInZone`, `FirebaseZoneScheduler`, and the `Firebase*` type aliases - -#### `@angular/fire/analytics` - -* `AngularFireAnalytics` now memozies `analytics.Analtyics` instances keyed to the `measurementId`, this prevents exceptions if you're using more than one `FirebaseApp` with the same `measurementId`. This is also needed as we are `providedIn: 'any'`. - -#### `@angular/fire/auth` - -* `AngularFireAuthModule` is now side-effect free and `AngularFireAuth` will dynamically import `firebase/auth` when a request is made -* `AngularFireAuth` has dropped the `auth` property and instead Promise Proxies the underlying Firebase `auth.Auth` instance - -#### `@angular/fire/auth-guard` - -* `AngularFireAuthGuard` and `canActivate` have dropped (attempted) support for raw `AuthPipe`s, as they were not functioning in AOT builds; you'll want to move to `AuthPipeGenerator`s - -#### `@angular/fire/database` - -* `AngularFireDatabaseModule` no longer imports `firebase/database` on it's own to remain side-effect free, you'll need to `import 'firebase/database'` on your own -* Dropped the `RealtimeDatabaseURL` and `DATABASE_URL` DI tokens in favor of `URL` - -#### `@angular/fire/firestore` - -* `AngularFirestoreModule` no longer imports `firebase/firestore` on it's own to remain side-effect free, you'll need to `import 'firebase/firestore'` on your own -* Dropped the `EnablePersistenceToken` DI token in favor of `ENABLE_PERSISTENCE` -* Dropped the `PersistenceSettingsToken` DI token in favor of `PERSISTENCE_SETTINGS` -* Dropped the `FirestoreSettingsToken` DI token in favor of `SETTINGS` - -#### `@angular/fire/functions` - -* Dropped the `FunctionsRegionToken` and `FUNCTIONS_REGION` DI tokens in favor of `REGION` -* Dropped the `FUNCTIONS_ORIGIN` DI token in favor of `ORIGIN` -* `AngularFireFunctionsModule` is now side-effect free and `AngularFireFunctions` will dynamically import `firebase/functions` when a request is made -* `AngularFireFunctions` has dropped the `functions` property and instead Promise Proxies the underlying Firebase `functions.Functions` instance - -#### `@angular/fire/messaging` - -* `AngularFireMessaging`'s dynamic import of `firebase/messaging` is now lazy, if you don't call any methods the SDK will not be loaded -* `AngularFireMessaging` has dropped the `messaging` property and instead Promise Proxies the underlying Firebase `messaging.Messaging` instance - -#### `@angular/fire/performance` - -* `AngularFirePerformance` has dropped the `performance` property and instead Promise Proxies the underlying Firebase `performance.Performance` instance - -#### `@angular/fire/storage` - -* `AngularFireStorageModule` no longer imports `firebase/storage` on it's own to remain side-effect free, you'll need to `import 'firebase/storage'` on your own -* Dropped `StorageBucket` DI token in favor of `BUCKET` - - -# [5.4.2](https://github.com/angular/angularfire2/compare/5.4.1...5.4.2) (2020-02-06) - -### Bug Fixes - -* **core:** fixing a problem with hot/cold observables resulting in missed events ([#2315](https://github.com/angular/angularfire2/issues/2315)) ([f24df35](https://github.com/angular/angularfire2/commit/f24df35)) - - - -# [5.4.1](https://github.com/angular/angularfire2/compare/5.4.0...5.4.1) (2020-02-05) - -### Bug Fixes - -* **auth:** `authState` should be using `onAuthStateChanged` ([#2308](https://github.com/angular/angularfire2/issues/2308)) ([9506f85](https://github.com/angular/angularfire2/commit/9506f85)) - - -# [5.4.0](https://github.com/angular/angularfire2/compare/5.3.1...5.4.0) (2020-02-01) - -### Features - -* **core:** Register AngularFire and Angular versions with the JS SDK ([6096c95](https://github.com/angular/angularfire2/commit/6096c95)) -* **ng-deploy:** add option for buildTarget ([#2281](https://github.com/angular/angularfire2/issues/2281)) ([28a4e54](https://github.com/angular/angularfire2/commit/28a4e54)) -* **core:** Major changes to the Zone.js wrapping to address SSR memory leaks and more ([#2294](https://github.com/angular/angularfire2/issues/2294)) ([56df941](https://github.com/angular/angularfire2/commit/56df941)) - - - -# [5.3.1](https://github.com/angular/angularfire2/compare/5.3.0...5.3.1) (2020-02-01) - -### Bug Fixes -* **schematics**: The schematics should be functional again. The version of `firebase-tools` we were installing when you called `ng add @angular/fire` was using deprecated API. ([#2285](https://github.com/angular/angularfire2/issues/2285)) ([5867eeb](https://github.com/angular/angularfire2/commit/5867eebbd2ec7eaad0bbc8da94e38aca1fe7580b)) -* **schematics**: fix issues with FS and Devkit Paths ([#2279](https://github.com/angular/angularfire2/issues/2279)) ([5ccf5db](https://github.com/angular/angularfire2/commit/5ccf5db3302be4a77529c33eda9ce39e5503b3c4)) -* **rc**: Need to `ensureInitialized()` ([#2290](https://github.com/angular/angularfire2/issues/2290)) ([0d95523](https://github.com/angular/angularfire2/commit/0d955231a0c91d8abd4effe0e02044f40451a891)) - - -# [5.3.0](https://github.com/angular/angularfire2/compare/5.2.3...5.3.0) (2020-01-07) - -AngularFire 5.3 introduces [Analytics](docs/analytics/getting-started.md) and [Remote Config](docs/remote-config/getting-started.md) modules. - - -# [5.2.3](https://github.com/angular/angularfire2/compare/5.2.1...5.2.3) (2019-11-12) - -### Bug Fixes - -* **build:** Make the build work on windows ([#2231](https://github.com/angular/angularfire2/issues/2231)) ([97d8532](https://github.com/angular/angularfire2/commit/97d8532)) -* **core:** Support Firebase 7 peer and fix zone instabilities with `AngularFirePerformanceModule` and the injectable `FirebaseApp` ([#2240](https://github.com/angular/angularfire2/issues/2240)) ([60fd575](https://github.com/angular/angularfire2/commit/60fd575)) -* **rtdb:** Allow update to take "Partial" ([#2169](https://github.com/angular/angularfire2/issues/2169)) ([ca43c8b](https://github.com/angular/angularfire2/commit/ca43c8b)) - - -# [5.2.2](https://github.com/angular/angularfire2/compare/5.2.1...5.2.2) (2019-11-12) - -`5.2.2` was mistakenly released to `@canary` due to a CI/CD bug. It was republished to `@latest` as `5.2.3`. - - -# [5.2.1](https://github.com/angular/angularfire2/compare/5.2.0...5.2.1) (2019-06-01) - -Removed unnecessary `peerDependencies` ([#2095](https://github.com/angular/angularfire2/pull/2095)) ([5e49442](https://github.com/angular/angularfire2/pull/2095/commits/5e49442)) - - -# [5.2.0](https://github.com/angular/angularfire2/compare/5.1.3...5.2.0) (2019-05-31) - -AngularFire 5.2 introduces support for Angular 8 and version 6 of the Firebase SDK. - -### Bug Fixes - -* **firestore:** Fix for builds targeting Node ([#2079](https://github.com/angular/angularfire2/issues/2079)) ([8a33826](https://github.com/angular/angularfire2/commit/8a33826)) -* **storage:** Typo in updateMetadata method ([#2029](https://github.com/angular/angularfire2/issues/2029)) ([6133296](https://github.com/angular/angularfire2/commit/6133296)) -* **messaging:** Allow `AngularFireMessaging` to be included in a server build ([#1938](https://github.com/angular/angularfire2/issues/1938)) ([9b870a9](https://github.com/angular/angularfire2/commit/9b870a9)) - -### Features - -* **performance:** AngularFire Performance Monitoring ([#2064](https://github.com/angular/angularfire2/issues/2064)) ([2469e77](https://github.com/angular/angularfire2/commit/2469e7721ffaea755ab6b95b66610e1495692342)) -* **auth-guard:** AngularFire Auth Guards ([#2016](https://github.com/angular/angularfire2/issues/2016)) ([e32164d](https://github.com/angular/angularfire2/commit/e32164d)) -* **firestore:** Added option to include document IDs on valueChanges() ([#1976](https://github.com/angular/angularfire2/issues/1976)) ([7108875](https://github.com/angular/angularfire2/commit/7108875)) -* **firestore:** Support Firestore Collection Group Queries ([#2066](https://github.com/angular/angularfire2/issues/2066)) ([c34c0f3](https://github.com/angular/angularfire2/commit/c34c0f3)) -* **functions:** Allow configuration of Functions Emulator Origin ([#2017](https://github.com/angular/angularfire2/issues/2017)) ([d12b4c5](https://github.com/angular/angularfire2/commit/d12b4c5)) -* **schematics:** ng deploy schematic ([#2046](https://github.com/angular/angularfire2/issues/2046)) ([be0a1fb](https://github.com/angular/angularfire2/commit/be0a1fb)) -* **firestore:** path on `AngularFirestoreCollection`'s `.doc` is optional ([#1974](https://github.com/angular/angularfire2/issues/1974)) ([c2354f8](https://github.com/angular/angularfire2/commit/c2354f8)) - - - -# [5.1.2](https://github.com/angular/angularfire2/compare/5.1.1...5.1.2) (2019-03-11) - - -### Bug Fixes - -* **afs:** No longer pull in the depreciated timestampsInSnapshots setting for Firebase 5.8 ([#2013](https://github.com/angular/angularfire2/issues/2013)) ([5df31c3](https://github.com/angular/angularfire2/commit/5df31c3)) - - -# [5.1.1](https://github.com/angular/angularfire2/compare/5.1.0...5.1.1) (2018-11-29) - - -### Bug Fixes - -* **functions:** Fix the default Functions region bug ([#1945](https://github.com/angular/angularfire2/issues/1945)) ([7d175b3](https://github.com/angular/angularfire2/commit/7d175b3)) - - - - -# [5.1.0](https://github.com/angular/angularfire2/compare/5.0.0-rc.12...5.1.0) (2018-10-17) - -### Features - -* **core:** Support Angular 7 without peer dependency warnings ([ed92c45](https://github.com/angular/angularfire2/commit/ed92c45)) -* **afs:** Support Firebase 5.5 and Firestore PersistenceSettings ([a9cf1ca](https://github.com/angular/angularfire2/commit/a9cf1ca)) -* **functions:** Support region configuration via `FunctionsRegionToken` ([8901617](https://github.com/angular/angularfire2/commit/8901617)) - - - -# [5.0.2](https://github.com/angular/angularfire2/compare/5.0.1...5.0.2) (2018-09-21) - -### Bug Fixes - -* **messaging:** Fix for the binding issue and onTokenRefresh for Messaging ([e170da1](https://github.com/angular/angularfire2/commit/e170da1)) - - - -# [5.0.1](https://github.com/angular/angularfire2/compare/5.0.0...5.0.1) (2018-09-07) - -Version 5.0.1 has been released for the deprecated `angularfire2` NPM library; this simply re-exports everything from `@angular/fire`; allowing you to use either `angularfire2` or `@angular/fire` in your imports and `package.json`. - -It will have it's dependency pinned to the corresponding minor and will be released alongside future `@angular/fire` releases for the rest of the 5.x series. - - - -# [5.0.0](https://github.com/angular/angularfire2/compare/5.0.0-rc.12...5.0.0) (2018-09-04) - - -### Bug Fixes - -* **firestore:** Better handle enablePersistence failures, esp. for Universal ([#1850](https://github.com/angular/angularfire2/issues/1850)) ([334ba7b](https://github.com/angular/angularfire2/commit/334ba7b)) -* **firestore:** Add document `get()`, the options argument for `get()` should be optional, and subscriptions to `get()` should be run in the Angular Zone([#1849](https://github.com/angular/angularfire2/issues/1849)) ([185943f](https://github.com/angular/angularfire2/commit/185943f)) - - -### Breaking changes - -5.0 is now final and we're published under `@angular/fire`. - -```bash -npm i --save firebase @angular/fire -``` - - - -# [5.0.0-rc.12](https://github.com/angular/angularfire2/compare/5.0.0-rc.11...5.0.0-rc.12) (2018-08-24) - -### Bug Fixes - -* **afs:** Gracefully handle duplicate emissions on modified/deleted ([#1825](https://github.com/angular/angularfire2/issues/1825)) ([76ff6c1](https://github.com/angular/angularfire2/commit/76ff6c1)) -* **core:** If an AngularFire observable was empty or threw, it could block Universal rendering ([#1832](https://github.com/angular/angularfire2/issues/1832)) ([36a8ff8](https://github.com/angular/angularfire2/commit/36a8ff8)) -* **core:** Fix for the Firebase ES export problems in Node ([#1821](https://github.com/angular/angularfire2/issues/1821)) ([f1014ee](https://github.com/angular/angularfire2/commit/f1014ee)) -* **storage:** Fix for zone issues on downloadURL and metadata, which blocked Universal rendering ([#1835](https://github.com/angular/angularfire2/issues/1835)) ([441607a](https://github.com/angular/angularfire2/commit/441607a)) - - -### Features - -* **firestore:** Added a `get` Observable ([#1824](https://github.com/angular/angularfire2/issues/1824)) ([9f34be8](https://github.com/angular/angularfire2/commit/9f34be8)) -* **messaging:** Introducing AngularFireMessaging ([#1749](https://github.com/angular/angularfire2/issues/1749)) ([26f7613](https://github.com/angular/angularfire2/commit/26f7613)) - - - -# [5.0.0-rc.11](https://github.com/angular/angularfire2/compare/5.0.0-rc.10...5.0.0-rc.11) (2018-06-13) - -### Bug Fixes - -* Fixed SSR compilation and misc. typing issues ([#1729](https://github.com/angular/angularfire2/issues/1729)) ([eed5802](https://github.com/angular/angularfire2/commit/eed5802)) - - - -# [5.0.0-rc.10](https://github.com/angular/angularfire2/compare/5.0.0-rc.9...5.0.0-rc.10) (2018-05-22) - -### Bug Fixes - -* **firestore:** the type passed to `AngularFirestoreCollection` from a document's sub-collection will now default to `DocumentData`, rather than `any`, if no type is specified ([#1662](https://github.com/angular/angularfire2/issues/1662)) ([2c2fe02](https://github.com/angular/angularfire2/commit/97c8656)) - -### Breaking change - -* **core:** AngularFire now depends only on the `firebase` NPM library, rather than `@firebase/*` and `@firebase/*-types`; this should simplify issues around keeping types in-sync and conflicts between package versions ([#1677](https://github.com/angular/angularfire2/issues/1677)) ([2c2fe02](https://github.com/angular/angularfire2/commit/53ad0d8)) - - - -# [5.0.0-rc.9](https://github.com/angular/angularfire2/compare/5.0.0-rc.8...5.0.0-rc.9) (2018-05-16) - - -### Bug Fixes - -* **core:** allow initializeApp to be used with AOT ([#1654](https://github.com/angular/angularfire2/issues/1654)) ([513565a](https://github.com/angular/angularfire2/commit/513565a)) -* **core:** Allow name + config deps to be optional ([#1641](https://github.com/angular/angularfire2/issues/1641)) ([a6af604](https://github.com/angular/angularfire2/commit/a6af604)) -* **firestore:** Fixed a bug where Firestore sub-collections were inheriting the type of the doc by default ([#1644](https://github.com/angular/angularfire2/issues/1644)) ([dff8ddf](https://github.com/angular/angularfire2/commit/dff8ddf)) - - -### Features - -* **auth:** Adding user and idTokenResult Observables to AngularFireAuth ([#1642](https://github.com/angular/angularfire2/issues/1642)) ([31045a9](https://github.com/angular/angularfire2/commit/31045a9)) -* **functions:** Adding AngularFireFunctions with httpCallable ([#1532](https://github.com/angular/angularfire2/issues/1532)) ([26f3f5f](https://github.com/angular/angularfire2/commit/26f3f5f)) -* **firestore:** types for collection, audit trail, state, and snapshot changes ([#1644](https://github.com/angular/angularfire2/issues/1644)) ([dff8ddf](https://github.com/angular/angularfire2/commit/dff8ddf)) -* **rtdb:** types for collection, audit trail, snapshot, and state changes ([#1643](https://github.com/angular/angularfire2/issues/1643)) ([2c2fe02](https://github.com/angular/angularfire2/commit/2c2fe02)) - - -### Breaking change - -* To deal with the initializeApp not being able to be used in AOT ([#1635](https://github.com/angular/angularfire2/issues/1635)) we removed `FirebaseAppConfigToken` and `FirebaseAppNameToken` and replaced them with a new `FirebaseNameOrConfigToken` which accepts either an app name string or a `FirebaseAppConfig` object. ([#1654](https://github.com/angular/angularfire2/issues/1654)) ([513565a](https://github.com/angular/angularfire2/commit/513565a)) -* **firestore:** If you do not specify a type to Document or Collection the default is now `DocumentData` ([#1644](https://github.com/angular/angularfire2/issues/1644)) ([dff8ddf](https://github.com/angular/angularfire2/commit/dff8ddf)) - - - - -# [5.0.0-rc.8](https://github.com/angular/angularfire2/compare/5.0.0-rc.7...5.0.0-rc.8) (2018-05-12) - -### Bug Fixes - -* Zone was already loaded, type is implied ([#1631](https://github.com/angular/angularfire2/issues/1631)) ([7d2fd53](https://github.com/angular/angularfire2/commit/7d2fd53)), closes [#1599](https://github.com/angular/angularfire2/issues/1599) - -### Features - -* Supporting Angular and rxjs 6 ([dd4a36c](https://github.com/angular/angularfire2/commit/dd4a36c)) -* Support Firebase JS SDK 5.0 ([#1628](https://github.com/angular/angularfire2/issues/1628)) ([b99bfa3](https://github.com/angular/angularfire2/commit/b99bfa3)) -* Support FirebaseAppConfig, clean up injection tokens ([#1627](https://github.com/angular/angularfire2/issues/1627)) ([57906bd](https://github.com/angular/angularfire2/commit/57906bd)) -* **firestore:** Support Firestore Settings, timestampsInSnapshots default to true ([#1629](https://github.com/angular/angularfire2/issues/1629)) ([570c0a7](https://github.com/angular/angularfire2/commit/570c0a7)) -* **auth:** Update to rxjs pipeable operators ([#1621](https://github.com/angular/angularfire2/issues/1621)) ([0c3b215](https://github.com/angular/angularfire2/commit/0c3b215)) -* **core:** Update to rxjs pipeable operators ([#1620](https://github.com/angular/angularfire2/issues/1620)) ([3fbbb7d](https://github.com/angular/angularfire2/commit/3fbbb7d)) -* **database:** Update to rxjs pipeable operators ([#1622](https://github.com/angular/angularfire2/issues/1622)) ([5c3681d](https://github.com/angular/angularfire2/commit/5c3681d)) -* **firestore:** Update to rxjs pipeable operators ([#1623](https://github.com/angular/angularfire2/issues/1623)) ([97b26e3](https://github.com/angular/angularfire2/commit/97b26e3)) -* **storage:** Update to rxjs pipeable operators ([#1624](https://github.com/angular/angularfire2/issues/1624)) ([014be21](https://github.com/angular/angularfire2/commit/014be21)) - -### Breaking changes - -* Due to the addition of a conflicting `FirebaseAppConfig` interface in Firebase 4.13 we've now changed our `FirebaseAppConfig` Injection Token to be `FirebaseOptionsToken` -* For consistency the `FirebaseAppName` Injection Token is now `FirebaseAppNameToken` -* rxjs 5 is no longer supported, upgrade to 6 ([see the rxjs migration guide for more information](https://github.com/ReactiveX/rxjs/blob/master/MIGRATION.md)) -* Firebase JS SDK 4.x is no longer supported, upgrade to 5 ([see the changelog for more information](https://firebase.google.com/support/release-notes/js#version_500_may_8_2018)) -* To mirror a change in Firebase 5.x, `downloadURL` was removed from `AngularFireUploadTask` - -### Known issues - -* Some users may experience failures compiling AOT while using `AngularFireModule.initializeApp(...)`, a work-around is available ([see #1635](https://github.com/angular/angularfire2/issues/1635)) - - -# [5.0.0-rc.7](https://github.com/angular/angularfire2/compare/5.0.0-rc.6...5.0.0-rc.7) (2018-05-04) - -### Bug Fixes - -* **afs:** workarounds for bugs in the Firebase JS SDK [#605](https://github.com/firebase/firebase-js-sdk/issues/605) and [#608](https://github.com/firebase/firebase-js-sdk/issues/608) ([#1540](https://github.com/angular/angularfire2/issues/1540)) ([14e78ec](https://github.com/angular/angularfire2/commit/14e78ec)) -* **app:** add automaticDataCollectionEnabled for compatability with Firebase JS SDK v4.13+ ([#1572](https://github.com/angular/angularfire2/issues/1572)) ([f2cf159](https://github.com/angular/angularfire2/commit/f2cf159)) - - -### Features - -* **firestore:** allow collection and doc from ref ([#1487](https://github.com/angular/angularfire2/issues/1487)) ([136f1e5](https://github.com/angular/angularfire2/commit/136f1e5)), closes [#1337](https://github.com/angular/angularfire2/issues/1337) -* `runOutsideAngular` for Universal / service worker compatability and allow advanced configuration with DI ([#1454](https://github.com/angular/angularfire2/issues/1454)) ([e343f13](https://github.com/angular/angularfire2/commit/e343f13)) - - - -# [5.0.0-rc.6](https://github.com/angular/angularfire2/compare/5.0.0-rc.4...v5.0.0-rc.6) (2018-01-26) - - -### Bug Fixes - -* Migrate imports to new Typings from 4.8.1 to resolve [#1385](https://github.com/angular/angularfire2/issues/1385) ([7ec51b2](https://github.com/angular/angularfire2/commit/7ec51b2)) -* Removing errant old import. Updating build with latest namespace. Fixing import for main [@firebase](https://github.com/firebase)/app. This resolves failing tests. ([a13bf9b](https://github.com/angular/angularfire2/commit/a13bf9b)) -* **afs:** fix di warning ([#1401](https://github.com/angular/angularfire2/issues/1401)) ([23ab383](https://github.com/angular/angularfire2/commit/23ab383)) -* **afs/typings:** valueChanges should return Observable ([#1321](https://github.com/angular/angularfire2/issues/1321)) ([aadc71a](https://github.com/angular/angularfire2/commit/aadc71a)) - - -### Features -* **storage:** Add Cloud Storage support ([e2283b1](https://github.com/angular/angularfire2/commit/e2283b1)) - - - - -# [5.0.0-rc.4](https://github.com/angular/angularfire2/compare/5.0.0-rc.3...5.0.0-rc.4) (2017-11-17) - - -### Bug Fixes - -* **afs:** added missing type to doc() ([#1286](https://github.com/angular/angularfire2/issues/1286)) ([3e00e16](https://github.com/angular/angularfire2/commit/3e00e16)) -* **afs:** allow document set options ([#1333](https://github.com/angular/angularfire2/issues/1333)) ([81018ed](https://github.com/angular/angularfire2/commit/81018ed)), closes [#1332](https://github.com/angular/angularfire2/issues/1332) -* **afs:** catch error when enabling persistence ([#1300](https://github.com/angular/angularfire2/issues/1300)) ([61245a3](https://github.com/angular/angularfire2/commit/61245a3)) -* **afs:** export interfaces ([#1277](https://github.com/angular/angularfire2/issues/1277)) ([4a21857](https://github.com/angular/angularfire2/commit/4a21857)) -* **db:** inherit generics in valueChanges interface ([10afd64](https://github.com/angular/angularfire2/commit/10afd64)), closes [#1214](https://github.com/angular/angularfire2/issues/1214) -* **db:** update should take a Partial object ([#1330](https://github.com/angular/angularfire2/issues/1330)) ([20e66f5](https://github.com/angular/angularfire2/commit/20e66f5)), closes [#1329](https://github.com/angular/angularfire2/issues/1329) - - - -# [5.0.0-rc.3](https://github.com/angular/angularfire2/compare/5.0.0-rc.2...5.0.0-rc.3) (2017-10-14) - - -### Bug Fixes - -* **afs:** change doc.update() parameter type to Partial ([#1247](https://github.com/angular/angularfire2/issues/1247)) ([297cabb](https://github.com/angular/angularfire2/commit/297cabb)), closes [#1245](https://github.com/angular/angularfire2/issues/1245) [#1215](https://github.com/angular/angularfire2/issues/1215) -* **rtdb:** Fixed null set handling, ordering, and cleaned up types ([#1264](https://github.com/angular/angularfire2/issues/1264)) ([eda1c41](https://github.com/angular/angularfire2/commit/eda1c41)) - - - -# [5.0.0-rc.2](https://github.com/angular/angularfire2/compare/5.0.0-rc.0...5.0.0-rc.2) (2017-10-05) - - -### Bug Fixes - -* **afs:** Allow multiple subscribers by using share, closes [#1191](https://github.com/angular/angularfire2/issues/1191) ([#1192](https://github.com/angular/angularfire2/issues/1192)) ([21522ab](https://github.com/angular/angularfire2/commit/21522ab)) -* **afs:** Don't filter empty changes (allow for null set) ([eb71edc](https://github.com/angular/angularfire2/commit/eb71edc)) -* **afs:** remove debugger statement from collection/changes.ts ([#1190](https://github.com/angular/angularfire2/issues/1190)) ([88a25e7](https://github.com/angular/angularfire2/commit/88a25e7)) -* **auth:** Clean up the authentication module ([8ab3803](https://github.com/angular/angularfire2/commit/8ab3803)) - - - - -# [5.0.0-rc.0](https://github.com/angular/angularfire2/compare/4.0.0-rc.2...v5.0.0-rc.0) (2017-10-03) - -### Features -* **AngularFirestore:** Module for Cloud Firestore ([90c8ede](https://github.com/angular/angularfire2/commit/90c8ede)) -* **New AngularFireDatabase API:** New API for the database [#1158](https://github.com/angular/angularfire2/issues/1158) - -### Breaking changes - -AngularFire 5.0 brings a new API for the Realtime Database. [See the migration doc for converting to the new API](https://github.com/angular/angularfire2/blob/master/docs/version-5-upgrade.md). If you want to stay on the old database API you can use: - -```ts -import { AngularFireModule } from 'angularfire2/database-deprecated'; -``` - - -# [4.0.0-rc.1](https://github.com/angular/angularfire2/compare/4.0.0-rc.0...v4.0.0-rc.1) (2017-06-02) - -### Breaking changes - -* **rc:** Update to Firebase JS SDK 4.0 ([9642f5](https://github.com/angular/angularfire2/commit/9642f589ba73add6d49a5818a1109028f8c7729b)) - -In version 4.0 of the Firebase SDK `onAuthStateChanged` is only fired on sign-in and sign-out, [see the Firebase JS SDK changelog for more information](https://firebase.google.com/support/release-notes/js#4.0.0). The added `AngularFireAuth.idToken: Observable` behaves as `authState` used to. - - -# [4.0.0-rc0](https://github.com/angular/angularfire2/compare/2.0.0-beta.8...v4.0.0-rc0) (2017-05-02) - - -### Bug Fixes - -* **auth:** Use the injected app ([980c447](https://github.com/angular/angularfire2/commit/980c447)) -* **build:** Add package.json files for deep paths ([cd5f2d1](https://github.com/angular/angularfire2/commit/cd5f2d1)), closes [#880](https://github.com/angular/angularfire2/issues/880) -* **database:** Fix test TypeScript errors ([750737c](https://github.com/angular/angularfire2/commit/750737c)), closes [#875](https://github.com/angular/angularfire2/issues/875) -* **database:** use switchMap when a list's query changes ([#831](https://github.com/angular/angularfire2/issues/831)) ([b85147d](https://github.com/angular/angularfire2/commit/b85147d)), closes [#830](https://github.com/angular/angularfire2/issues/830) - - -### Features - -* **auth:** New Auth API ([12aa422](https://github.com/angular/angularfire2/commit/12aa422)) -* **database:** Add AngularFireDatabaseModule ([b388627](https://github.com/angular/angularfire2/commit/b388627)) -* **database:** support optional endAt/equalTo key ([#838](https://github.com/angular/angularfire2/issues/838)) ([e146492](https://github.com/angular/angularfire2/commit/e146492)), closes [#837](https://github.com/angular/angularfire2/issues/837) -* **rc:** Implement rc0 API ([398e4e2](https://github.com/angular/angularfire2/commit/398e4e2)) - - - -# [2.0.0-beta.8](https://github.com/angular/angularfire2/compare/2.0.0-beta7...v2.0.0-beta.8) (2017-02-16) - - -### Bug Fixes - -* **database:** allow null values for equalTo, etc. ([#809](https://github.com/angular/angularfire2/issues/809)) ([561e7b7](https://github.com/angular/angularfire2/commit/561e7b7)) -* **database:** call apply instead of call ([7a85bd2](https://github.com/angular/angularfire2/commit/7a85bd2)) -* **database:** retrieve initial list content once ([#820](https://github.com/angular/angularfire2/issues/820)) ([5c5ff7b](https://github.com/angular/angularfire2/commit/5c5ff7b)), closes [#819](https://github.com/angular/angularfire2/issues/819) -* **database:** store unwrapped snapshots ([9f3c47b](https://github.com/angular/angularfire2/commit/9f3c47b)), closes [#791](https://github.com/angular/angularfire2/issues/791) -* **utils:** Make object $key and $exists properties non-enumerable ([253401f](https://github.com/angular/angularfire2/commit/253401f)) -* **utils:** Minor formatting improvement ([fc3774a](https://github.com/angular/angularfire2/commit/fc3774a)) - - -### Features - -* **database:** adds auditTime for queries ([f9cb5c3](https://github.com/angular/angularfire2/commit/f9cb5c3)), closes [#389](https://github.com/angular/angularfire2/issues/389) [#770](https://github.com/angular/angularfire2/issues/770) -* **database:** support the optional startAt key ([#821](https://github.com/angular/angularfire2/issues/821)) ([c469b11](https://github.com/angular/angularfire2/commit/c469b11)) - - - - -# [2.0.0-beta.7](https://github.com/angular/angularfire2/compare/2.0.0-beta.6...v2.0.0-beta.7) (2017-01-13) - - -### Breaking changes - -* **auth:** Remove `FirebaseAuth` in favor of `AngularFireAuth`. ([d422e6])(https://github.com/angular/angularfire2/commit/d422e62b46a80d9fb12c9a9e2cf1cf2f7db04dd3) - -### Bug Fixes - -* **aot:** Remove AuthBackend param for AOT support ([f875360](https://github.com/angular/angularfire2/commit/f875360)) -* **auth_backend:** Update logout method to return a promise ([169ce64](https://github.com/angular/angularfire2/commit/169ce64)), closes [#583](https://github.com/angular/angularfire2/issues/583) -* **config:** Add messagingSenderId to FirebaseAppConfig ([9c84869](https://github.com/angular/angularfire2/commit/9c84869)) -* **database:** Allow null values for equalTo, etc. ([70a3e94](https://github.com/angular/angularfire2/commit/70a3e94)), closes [#704](https://github.com/angular/angularfire2/issues/704) -* **database:** Removed unused query option ([9cbc59b](https://github.com/angular/angularfire2/commit/9cbc59b)), closes [#706](https://github.com/angular/angularfire2/issues/706) -* **list:** Fix FirebaseListObservable emit as array bug [#574](https://github.com/angular/angularfire2/issues/574) ([ce3de04](https://github.com/angular/angularfire2/commit/ce3de04)) -* **module:** Conditionally pass app name ([8427009](https://github.com/angular/angularfire2/commit/8427009)) - -### Features - -* **module:** Add a custom FirebaseApp name ([73a3e26](https://github.com/angular/angularfire2/commit/73a3e26)) - - - - -# [2.0.0-beta.6-preview](https://github.com/angular/angularfire2/compare/2.0.0-beta.5...v2.0.0-beta.6-preview) (2016-11-02) - - -### Bug Fixes - -* **Database:** use Zone scheduler for object and list factories ([e18da0e](https://github.com/angular/angularfire2/commit/e18da0e)), closes [#637](https://github.com/angular/angularfire2/issues/637) -* **AoT:** change constructor param interface type annotation to any ([2c0a57f](https://github.com/angular/angularfire2/commit/2c0a57f)) -* **build:** Fix npm test and test:watch commands for windows ([86b4b24](https://github.com/angular/angularfire2/commit/86b4b24)), closes [#217](https://github.com/angular/angularfire2/issues/217) -* **database:** Add $ref to observables ([#447](https://github.com/angular/angularfire2/issues/447)) ([a53fac0](https://github.com/angular/angularfire2/commit/a53fac0)), closes [#294](https://github.com/angular/angularfire2/issues/294) -* **imports:** add firebase imports to all places that reference firebase namespace ([c3a954c](https://github.com/angular/angularfire2/commit/c3a954c)), closes [#525](https://github.com/angular/angularfire2/issues/525) - - -### Features - -* **docs:** Add AoT installation and setup ([#546](https://github.com/angular/angularfire2/issues/546)) ([7c20d13](https://github.com/angular/angularfire2/commit/7c20d13)) -* **docs:** update installation guide to latest cli version ([#519](https://github.com/angular/angularfire2/issues/519)) ([648666f](https://github.com/angular/angularfire2/commit/648666f)) -* **hmr:** Add Hot module reloading ([c32a008](https://github.com/angular/angularfire2/commit/c32a008)) - -### Notes - -A TypeScript issue with the previous release has been fixed, where errors about a missing `firebase` namespace were reported. For -applications that worked around this issue by manually adding the `firebase.d.ts` typings to `tsconfig.json`, those typings -should now be removed since the firebase namespace should automatically be resolved within AngularFire. -See [this issue](https://github.com/angular/angularfire2/issues/525) for more context. - - -# [2.0.0-beta.5](https://github.com/angular/angularfire2/compare/2.0.0-beta.4...v2.0.0-beta.5) (2016-09-15) - - -### Bug Fixes - -* **docs:** Remove [@next](https://github.com/next) install ([5984a99](https://github.com/angular/angularfire2/commit/5984a99)) -* **docs:** typos ([197026a](https://github.com/angular/angularfire2/commit/197026a)) -* **docs:** Update for beta.4 ([f2d5ba5](https://github.com/angular/angularfire2/commit/f2d5ba5)) -* **docs:** Update for beta.4 ([b347e16](https://github.com/angular/angularfire2/commit/b347e16)) -* **firebase_*_factory.js:** Fix calls to off() which inadvertently cancel all listeners on the path ([#469](https://github.com/angular/angularfire2/issues/469)) ([b4fb281](https://github.com/angular/angularfire2/commit/b4fb281)), closes [#443](https://github.com/angular/angularfire2/issues/443) -* **package:** Version number ([986685a](https://github.com/angular/angularfire2/commit/986685a)) - - -### Features - -* **utils:** Add $exists method to AFUnwrappedSnapshot ([#471](https://github.com/angular/angularfire2/issues/471)) ([f67aab1](https://github.com/angular/angularfire2/commit/f67aab1)) -* upgrade to RC7 ([#505](https://github.com/angular/angularfire2/issues/505)) ([2410b2d](https://github.com/angular/angularfire2/commit/2410b2d)) - -### BREAKING CHANGES - -The way this project is packaged has changed to be consistent with other Angular packages. -Previously: - - * The project just consisted of CommonJS modules, with `angularfire2.js` as the main entry point. - * The project provided an `es6` directory which contained es2015 modules and es2015 JS - * Package.json included `main` and `jsnext:main` fields, pointing to `angularfire2.js` and `es6/angularfire2.js`, respectively. - -Now: - - * The project ships ES2015 modules with ES5 JS at the root, as well as an ES5 UMD bundle at `bundles/angulafire2.umd.js` - * The `main` field of `package.json` points to `bundles/angularfire2.umd.js`. - * Instead of `jsnext:main`, we're using the `module` field of package.json to point to `index.js`. - * Instead of `angularfire2.js` being the main entry point, an `index.js` has been added (though angulafire2.js hasn't changed significantly). - -If you're using Rollup or Webpack, they should _just work_ with this new setup (please open issues if not). If using SystemJS, you should be able to -add `format: 'esm'` inside of the packages configuration, and it should load and parse the es2015 modules correctly. - -The addition of the umd bundle will also make it possible to use AngularFire2 in a ` + + + + +
+ + + +
+
+ {% block content%} + {{ content | safe }} + {% endblock %} +
+
+ + + +
+ + + diff --git a/site/src/_includes/guide.njk b/site/src/_includes/guide.njk new file mode 100644 index 000000000..c90443d47 --- /dev/null +++ b/site/src/_includes/guide.njk @@ -0,0 +1,11 @@ +{% extends "default.njk" %} +{% import "next-prev.njk" as nextprev with context %} + +{% block content %} + +

{{ title }}

+ + {{ content | safe }} + {{ nextprev.contextgrid("guides") }} + +{% endblock %} diff --git a/site/src/_includes/next-prev.njk b/site/src/_includes/next-prev.njk new file mode 100644 index 000000000..703894789 --- /dev/null +++ b/site/src/_includes/next-prev.njk @@ -0,0 +1,65 @@ + +{% macro item(direction, title, url) %} + {% set justifyDirection = 'justify-center lg:justify-start xl:justify-start' %} + {% set textDirection = 'text-center lg:text-left xl:text-left' %} + + {% if direction === 'Next' %} + {% set justifyDirection = 'justify-center lg:justify-end xl:justify-end' %} + {% set textDirection = 'text-center lg:text-right xl:text-right' %} + {% endif%} + + +
+
+ {{ direction }} +
+ + {{ title }} + +
+
+{% endmacro %} + +{% macro grid(prevRecord, nextRecord) %} + +
+ + + +
+ +{% endmacro %} + +{% macro contextgrid(tag) %} + {# Get the current page index from eleventyNavigation and then get the next and previous pages #} + + {% if eleventyNavigation.parent %} + {% set children = nextprev[eleventyNavigation.parent].children %} + {% set prevRecord = children | findPreviousEntry(eleventyNavigation) %} + {% set nextRecord = children | findNextEntry(eleventyNavigation) %} + + {{ grid(prevRecord, nextRecord) }} + + {% endif%} + +{% endmacro %} diff --git a/site/src/_includes/side-nav.njk b/site/src/_includes/side-nav.njk new file mode 100644 index 000000000..b1772ea6f --- /dev/null +++ b/site/src/_includes/side-nav.njk @@ -0,0 +1,45 @@ +{# +Type of sectionEntry +[{ + "key": "Mammals", + "url": "/mammals/", + "title": "Mammals", + "children": [{ + "key": "Humans", + "parentKey": "Mammals", + "url": "/humans/", + "title": "Humans" + }, + { + "key": "Dogs", + "parentKey": "Mammals", + "url": "/dogs/", + "title": "Dogs" + }] + }] +#} + +{% macro navsection(sectionEntry, page) %} + +{% endmacro %} diff --git a/site/src/analytics/analytics.11tydata.json b/site/src/analytics/analytics.11tydata.json new file mode 100644 index 000000000..b924da1f3 --- /dev/null +++ b/site/src/analytics/analytics.11tydata.json @@ -0,0 +1,4 @@ +{ + "layout": "guide.njk", + "tags": "guides" +} diff --git a/docs/analytics/getting-started.md b/site/src/analytics/getting-started.md similarity index 90% rename from docs/analytics/getting-started.md rename to site/src/analytics/getting-started.md index 76ee26859..33753cf89 100644 --- a/docs/analytics/getting-started.md +++ b/site/src/analytics/getting-started.md @@ -1,8 +1,15 @@ -# Getting started with Google Analytics +--- +title: Getting started +eleventyNavigation: + key: Getting started + parent: Analytics +--- + +## Google Analytics `AngularFireAnalytics` dynamically imports the `firebase/analytics` library and provides a promisified version of the [Firebase Analytics SDK (`firebase.analytics.Analytics`)](https://firebase.google.com/docs/reference/js/firebase.analytics.Analytics.html). -### API: +## API Summary ```ts class AngularFireAnalytics { @@ -24,7 +31,7 @@ DEBUG_MODE = InjectionToken; CONFIG = InjectionToken; ``` -### Usage: +## Usage ```ts import { AngularFireAnalyticsModule } from '@angular/fire/analytics'; @@ -38,7 +45,7 @@ import { AngularFireAnalyticsModule } from '@angular/fire/analytics'; export class AppModule { } ``` -`AngularFireAnalyticsModule` will dyanamically import and configure `firebase/analytics`. A `page_view` event will automatically be logged (see `CONFIG` below if you wish to disable this behavior.) +`AngularFireAnalyticsModule` will dynamically import and configure `firebase/analytics`. A `page_view` event will automatically be logged (see `CONFIG` below if you wish to disable this behavior.) In your component you can then dependency inject `AngularFireAnalytics` and make calls against the SDK: @@ -95,8 +102,6 @@ export class AppModule { } ## Configuration with Dependency Injection -### Configure Google Analtyics with `CONFIG` - Using the `CONFIG` DI Token (*default: {}*) will allow you to configure Google Analytics. E.g, you could skip sending the initial `page_view` event, anonymize IP addresses, and disallow ads personalization signals for all events like so: ```ts @@ -120,16 +125,16 @@ export class AppModule { } See the gtag.js documentation to learn of the different configuration options at your disposal. -### Use DebugView `DEBUG_MODE` +## Use DebugView To use [DebugView in Analytics](https://console.firebase.google.com/project/_/analytics/debugview) set `DEBUG_MODE` to `true` (*default: false*). -### Track deployments with `APP_NAME` and `APP_VERSION` +## Track deployments If you provide `APP_NAME` and `APP_VERSION` (*default: undefined*) you will be able to [track version adoption](https://console.firebase.google.com/project/_/analytics/latestrelease) of your PWA. -### Disable analytics collection via `COLLECTION_ENABLED` +## Disable collection If you set `COLLECTION_ENABLED` (*default: true*) to `false` then analytics collection will be disabled for this app on this device. To opt back in to analytics collection you could then call `setAnalyticsCollectionEnabled(true)`. -Putting these APIs to use with cookies would allow you to create a flexible analytics collection scheme that would respect your user's desire for privacy. +Putting these APIs to use with cookies would allow you to create a flexible analytics collection scheme that would respect your user's preferences and data collection policies. diff --git a/site/src/analytics/index.md b/site/src/analytics/index.md new file mode 100644 index 000000000..4bcfd0e99 --- /dev/null +++ b/site/src/analytics/index.md @@ -0,0 +1,6 @@ +--- +eleventyNavigation: + key: Analytics + order: 5 +--- + diff --git a/site/src/assets/GoogleSans-Bold.woff2 b/site/src/assets/GoogleSans-Bold.woff2 new file mode 100644 index 000000000..f69d92101 Binary files /dev/null and b/site/src/assets/GoogleSans-Bold.woff2 differ diff --git a/site/src/assets/GoogleSans-Medium.woff2 b/site/src/assets/GoogleSans-Medium.woff2 new file mode 100644 index 000000000..ab01e3bbb Binary files /dev/null and b/site/src/assets/GoogleSans-Medium.woff2 differ diff --git a/site/src/assets/GoogleSans-Regular.woff2 b/site/src/assets/GoogleSans-Regular.woff2 new file mode 100644 index 000000000..7803436c9 Binary files /dev/null and b/site/src/assets/GoogleSans-Regular.woff2 differ diff --git a/site/src/assets/Roboto-900.woff2 b/site/src/assets/Roboto-900.woff2 new file mode 100644 index 000000000..802499d3f Binary files /dev/null and b/site/src/assets/Roboto-900.woff2 differ diff --git a/site/src/assets/Roboto-Italic.woff2 b/site/src/assets/Roboto-Italic.woff2 new file mode 100644 index 000000000..2741d4f08 Binary files /dev/null and b/site/src/assets/Roboto-Italic.woff2 differ diff --git a/site/src/assets/Roboto-Regular.woff2 b/site/src/assets/Roboto-Regular.woff2 new file mode 100644 index 000000000..1a5370151 Binary files /dev/null and b/site/src/assets/Roboto-Regular.woff2 differ diff --git a/site/src/assets/RobotoMono-Regular.woff2 b/site/src/assets/RobotoMono-Regular.woff2 new file mode 100644 index 000000000..ed384d22f Binary files /dev/null and b/site/src/assets/RobotoMono-Regular.woff2 differ diff --git a/site/src/assets/corner.svg b/site/src/assets/corner.svg new file mode 100644 index 000000000..ba786e975 --- /dev/null +++ b/site/src/assets/corner.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/site/src/assets/firebase-logo.svg b/site/src/assets/firebase-logo.svg new file mode 100644 index 000000000..c2313a7a7 --- /dev/null +++ b/site/src/assets/firebase-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/site/src/auth/auth.11tydata.json b/site/src/auth/auth.11tydata.json new file mode 100644 index 000000000..b924da1f3 --- /dev/null +++ b/site/src/auth/auth.11tydata.json @@ -0,0 +1,4 @@ +{ + "layout": "guide.njk", + "tags": "guides" +} diff --git a/docs/auth/getting-started.md b/site/src/auth/getting-started.md similarity index 83% rename from docs/auth/getting-started.md rename to site/src/auth/getting-started.md index 764adad93..cf3edae22 100644 --- a/docs/auth/getting-started.md +++ b/site/src/auth/getting-started.md @@ -1,4 +1,11 @@ -# 5. Getting started with Firebase Authentication +--- +title: Getting started +eleventyNavigation: + key: Getting started + parent: Auth +--- + +## Using AngularFireAuth `AngularFireAuth.user` provides you an `Observable` to monitor your application's authentication State. @@ -11,11 +18,11 @@ the Firebase docs for more information on what methods are available.](https://f ```ts import { Component } from '@angular/core'; import { AngularFireAuth } from '@angular/fire/auth'; -import { auth } from 'firebase/app'; +import firebase from 'firebase/app'; @Component({ selector: 'app-root', - template: ` + template: `{%raw%}

Hello {{ user.displayName }}!

@@ -24,13 +31,13 @@ import { auth } from 'firebase/app';

Please login.

- `, + {%endraw%}`, }) export class AppComponent { constructor(public auth: AngularFireAuth) { } login() { - this.auth.signInWithPopup(new auth.GoogleAuthProvider()); + this.auth.signInWithPopup(new firebase.auth.GoogleAuthProvider()); } logout() { this.auth.signOut(); diff --git a/site/src/auth/index.md b/site/src/auth/index.md new file mode 100644 index 000000000..9fc2864fe --- /dev/null +++ b/site/src/auth/index.md @@ -0,0 +1,5 @@ +--- +eleventyNavigation: + key: Auth + order: 3 +--- diff --git a/site/src/auth/route-guards.md b/site/src/auth/route-guards.md new file mode 100644 index 000000000..e788253fa --- /dev/null +++ b/site/src/auth/route-guards.md @@ -0,0 +1,109 @@ +--- +title: Route guards +eleventyNavigation: + key: Route guards + parent: Auth +--- + +## Route users with AngularFire guards + +`AuthGuard` provides a prebuilt [`canActivate` Router Guard](https://angular.io/api/router/CanActivate) using `AngularFireAuth`. By default unauthenticated users are not permitted to navigate to protected routes: + +```ts +import { AuthGuard } from '@angular/fire/auth-guard'; + +export const routes: Routes = [ + { path: '', component: AppComponent }, + { path: 'items', component: ItemListComponent, canActivate: [AuthGuard] }, +] +``` + +## Customizing the behavior + +To customize the behavior of `AuthGuard`, you can pass an RXJS pipe through the route data's `authGuardPipe` key. + +The `auth-guard` module provides the following pre-built pipes: + +| Exported pipe | Functionality | +|-|-| +| `loggedIn` | The default pipe, rejects if the user is not authenticated. | +| `isNotAnonymous` | Rejects if the user is anonymous | +| `emailVerified` | Rejects if the user's email is not verified | +| `hasCustomClaim(claim)` | Rejects if the user does not have the specified claim | +| `redirectUnauthorizedTo(redirect)` | Redirect unauthenticated users to a different route | +| `redirectLoggedInTo(redirect)` | Redirect authenticated users to a different route | + +Example use: + +```ts +import { AuthGuard, hasCustomClaim, redirectUnauthorizedTo, redirectLoggedInTo } from '@angular/fire/auth-guard'; + +const adminOnly = () => hasCustomClaim('admin'); +const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['login']); +const redirectLoggedInToItems = () => redirectLoggedInTo(['items']); +const belongsToAccount = (next) => hasCustomClaim(`account-${next.params.id}`); + +export const routes: Routes = [ + { path: '', component: AppComponent }, + { path: 'login', component: LoginComponent, canActivate: [AuthGuard], data: { authGuardPipe: redirectLoggedInToItems }}, + { path: 'items', component: ItemListComponent, canActivate: [AuthGuard], data: { authGuardPipe: redirectUnauthorizedToLogin }}, + { path: 'admin', component: AdminComponent, canActivate: [AuthGuard], data: { authGuardPipe: adminOnly }}, + { path: 'accounts/:id', component: AdminComponent, canActivate: [AuthGuard], data: { authGuardPipe: belongsToAccount }} +]; +``` + +Use the provided `canActivate` helper and spread syntax to make your routes more readable: + +```ts +import { canActivate } from '@angular/fire/auth-guard'; + +export const routes: Routes = [ + { path: '', component: AppComponent }, + { path: 'login', component: LoginComponent, ...canActivate(redirectLoggedInToItems) }, + { path: 'items', component: ItemListComponent, ...canActivate(redirectUnauthorizedToLogin) }, + { path: 'admin', component: AdminComponent, ...canActivate(adminOnly) }, + { path: 'accounts/:id', component: AdminComponent, ...canActivate(belongsToAccount) } +]; +``` + +## Compose your own pipes + +`AuthGuard` pipes are RXJS operators which transform an optional User to a boolean or Array (for redirects). You can easily build your own to customize behavior further: + +```ts +import { map } from 'rxjs/operators'; + +// This pipe redirects a user to their "profile edit" page or the "login page" if they're unauthenticated +// { path: 'profile', ...canActivate(redirectToProfileEditOrLogin) } +const redirectToProfileEditOrLogin = () => map(user => user ? ['profiles', user.uid, 'edit'] : ['login']); +``` + +The `auth-guard` modules provides a `customClaims` operator to reduce boiler plate when checking a user's claims: + +```ts +import { pipe } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { customClaims } from '@angular/fire/auth-guard'; + +// This pipe will only allow users with the editor role to access the route +// { path: 'articles/:id/edit', component: ArticleEditComponent, ...canActivate(editorOnly) } +const editorOnly = () => pipe(customClaims, map(claims => claims.role === 'editor')); +``` + +## Using router state + +`AuthGuard` will also accept `AuthPipeGenerator`s which generate `AuthPipe`s given the router state: + +```ts +import { pipe } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { customClaims } from '@angular/fire/auth-guard'; + +// Only allow navigation to the route if :userId matches the authenticated user's uid +// { path: 'user/:userId/edit', component: ProfileEditComponent, ...canActivate(onlyAllowSelf) } +const onlyAllowSelf = (next) => map(user => !!user && next.params.userId === user.uid); + +// Only allow navigation to the route if the user has a custom claim matching :accountId +// { path: 'accounts/:accountId/billing', component: BillingDetailsComponent, ...canActivate(accountAdmin) } +const accountAdmin = (next) => pipe(customClaims, map(claims => claims[`account-${next.params.accountId}-role`] === 'admin')); +``` diff --git a/site/src/favicon.ico b/site/src/favicon.ico new file mode 100644 index 000000000..6c4d1e73c Binary files /dev/null and b/site/src/favicon.ico differ diff --git a/site/src/firestore/collections.md b/site/src/firestore/collections.md new file mode 100644 index 000000000..c993d77de --- /dev/null +++ b/site/src/firestore/collections.md @@ -0,0 +1,330 @@ +--- +title: Collections +eleventyNavigation: + key: Collections + parent: Firestore +--- + +## Documents in AngularFirestore + +Cloud Firestore is a NoSQL, document-oriented database. Unlike a SQL database, there are no tables or rows. Instead, you store data in *documents*, which are organized into *collections*. +Each *document* contains a set of key-value pairs. Cloud Firestore is optimized for storing large collections of small documents. + +## Using `AngularFirestoreCollection` + +The `AngularFirestoreCollection` service is a wrapper around the native Firestore SDK's [`CollectionReference`](https://firebase.google.com/docs/reference/js/v8/firebase.firestore.CollectionReference) and [`Query`](hhttps://firebase.google.com/docs/reference/js/v8/firebase.firestore.Query) types. It is a generic service that provides you with a strongly typed set of methods for manipulating and streaming data. This service is designed for use as an `@Injectable()`. + +```ts +import { Component } from '@angular/core'; +import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore'; +import { Observable } from 'rxjs'; + +export interface Item { name: string; } + +@Component({ + selector: 'app-root', + template: ` +
    +
  • + {{ item.name }} +
  • +
+ ` +}) +export class AppComponent { + private itemsCollection: AngularFirestoreCollection; + items: Observable; + constructor(private afs: AngularFirestore) { + this.itemsCollection = afs.collection('items'); + this.items = this.itemsCollection.valueChanges(); + } + addItem(item: Item) { + this.itemsCollection.add(item); + } +} +``` + +The `AngularFirestoreCollection` is a service you use to create streams of the collection and perform data operations on the underyling collection. + +## The `DocumentChangeAction` type + +With the exception of the `valueChanges()`, each streaming method returns an Observable of `DocumentChangeAction[]`. + +A `DocumentChangeAction` gives you the `type` and `payload` properties. The `type` tells when what `DocumentChangeType` operation occured (`added`, `modified`, `removed`). The `payload` property is a `DocumentChange` which provides you important metadata about the change and a `doc` property which is the `DocumentSnapshot`. + +```ts +interface DocumentChangeAction { + //'added' | 'modified' | 'removed'; + type: DocumentChangeType; + payload: DocumentChange; +} + +interface DocumentChange { + type: DocumentChangeType; + doc: DocumentSnapshot; + oldIndex: number; + newIndex: number; +} + +interface DocumentSnapshot { + exists: boolean; + ref: DocumentReference; + id: string; + metadata: SnapshotMetadata; + data(): DocumentData; + get(fieldPath: string): any; +} +``` + +## Streaming collection data + +There are multiple ways of streaming collection data from Firestore. + +## `valueChanges({ idField?: string })` + +*What is it?* - The current state of your collection. Returns an Observable of data as a synchronized array of JSON objects. All Snapshot metadata is stripped and just the document data is included. Optionally, you can pass an options object with an `idField` key containing a string. If provided, the returned JSON objects will include their document ID mapped to a property with the name provided by `idField`. + +*Why would you use it?* - When you just need a list of data. No document metadata is attached to the resulting array which makes it simple to render to a view. + +*When would you not use it?* - When you need a more complex data structure than an array. + +*Best practices* - Use this method to display data on a page. It's simple but effective. Use `.snapshotChanges()` once your needs become more complex. + +#### Example of persisting a Document Id + +```ts +import { Component } from '@angular/core'; +import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore'; +import { Observable } from 'rxjs'; + +export interface Item { id: string; name: string; } + +@Component({ + selector: 'app-root', + template: `{%raw%} +
    +
  • + {{ item.name }} +
  • +
+ {%endraw%}` +}) +export class AppComponent { + private itemsCollection: AngularFirestoreCollection; + items: Observable; + constructor(private readonly afs: AngularFirestore) { + this.itemsCollection = afs.collection('items'); + this.items = this.itemsCollection.valueChanges({ idField: 'customID' }); + } + addItem(name: string) { + // Persist a document id + const id = this.afs.createId(); + const item: Item = { id, name }; + this.itemsCollection.doc(id).set(item); + } +} +``` + +### `snapshotChanges()` + +*What is it?* - The current state of your collection. Returns an Observable of data as a synchronized array of `DocumentChangeAction[]`. + +*Why would you use it?* - When you need a list of data but also want to keep around metadata. Metadata provides you the underyling `DocumentReference`, document id, and array index of the single document. Having the document's id around makes it easier to use data manipulation methods. This method gives you more horsepower with other Angular integrations such as ngrx, forms, and animations due to the `type` property. The `type` property on each `DocumentChangeAction` is useful for ngrx reducers, form states, and animation states. + +*When would you not use it?* - When you need a more complex data structure than an array or if you need to process changes as they occur. This array is synchronized with the remote and local changes in Firestore. + +*Best practices* - Use an observable operator to transform your data from `.snapshotChanges()`. Don't return the `DocumentChangeAction[]` to the template. See the example below. + +#### Example + +```ts +import { Component } from '@angular/core'; +import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +export interface Shirt { name: string; price: number; } +export interface ShirtId extends Shirt { id: string; } + +@Component({ + selector: 'app-root', + template: `{%raw%} +
    +
  • + {{ shirt.name }} is {{ shirt.price }} +
  • +
+ {%endraw%}` +}) +export class AppComponent { + private shirtCollection: AngularFirestoreCollection; + shirts: Observable; + constructor(private readonly afs: AngularFirestore) { + this.shirtCollection = afs.collection('shirts'); + // .snapshotChanges() returns a DocumentChangeAction[], which contains + // a lot of information about "what happened" with each change. If you want to + // get the data and the id use the map operator. + this.shirts = this.shirtCollection.snapshotChanges().pipe( + map(actions => actions.map(a => { + const data = a.payload.doc.data() as Shirt; + const id = a.payload.doc.id; + return { id, ...data }; + })) + ); + } +} +``` + +### `stateChanges()` + +*What is it?* - Returns an Observable of the most recent changes as a `DocumentChangeAction[]`. + +*Why would you use it?* - The above methods return a synchronized array sorted in query order. `stateChanges()` emits changes as they occur rather than syncing the query order. This works well for ngrx integrations as you can build your own data structure in your reducer methods. + +*When would you not use it?* - When you just need a list of data. This is a more advanced usage of AngularFirestore. + +#### Example + +```ts +import { Component } from '@angular/core'; +import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +export interface AccountDeposit { description: string; amount: number; } +export interface AccountDepositId extends AccountDeposit { id: string; } + +@Component({ + selector: 'app-root', + template: `{%raw%} +
    +
  • + {{ deposit.description }} for {{ deposit.amount }} +
  • +
+ {%endraw%}` +}) +export class AppComponent { + private depositCollection: AngularFirestoreCollection; + deposits: Observable; + constructor(private readonly afs: AngularFirestore) { + this.depositCollection = afs.collection('deposits'); + this.deposits = this.depositCollection.stateChanges(['added']).pipe( + map(actions => actions.map(a => { + const data = a.payload.doc.data() as AccountDeposit; + const id = a.payload.doc.id; + return { id, ...data }; + })) + ); + } +} +``` + +### `auditTrail()` + +*What is it?* - Returns an Observable of `DocumentChangeAction[]` as they occur. Similar to `stateChanges()`, but instead it keeps around the trail of events as an array. + +*Why would you use it?* - This method is like `stateChanges()` except it is not ephemeral. It collects each change in an array as they occur. This is useful for ngrx integrations where you need to replay the entire state of an application. This also works as a great debugging tool for all applications. You can simply write `afs.collection('items').auditTrail().subscribe(console.log)` and check the events in the console as they occur. + +*When would you not use it?* - When you just need a list of data. This is a more advanced usage of AngularFirestore. + +#### Example + +```ts +import { Component } from '@angular/core'; +import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +export interface AccountLogItem { description: string; amount: number; } +export interface AccountLogItemId extends AccountLogItem { id: string; } + +@Component({ + selector: 'app-root', + template: `{%raw%} +
    +
  • + {{ log.description }} for {{ log.amount }} +
  • +
+ {%endraw%}` +}) +export class AppComponent { + private accountLogCollection: AngularFirestoreCollection; + accountLogs: Observable; + constructor(private readonly afs: AngularFirestore) { + this.accountLogCollection = afs.collection('accountLog'); + this.accountLogs = this.accountLogCollection.auditTrail().pipe( + map(actions => actions.map(a => { + const data = a.payload.doc.data() as AccountLogItem; + const id = a.payload.doc.id; + return { id, ...data }; + })) + ); + } +} +``` + +### Limiting events + +There are three `DocumentChangeType`s in Firestore: `added`, `removed`, and `modified`. Each streaming method listens to all three by default. However, you may only be intrested in one of these events. You can specify which events you'd like to use through the first parameter of each method: + +#### Basic example + +```ts +constructor(private afs: AngularFirestore): { + this.itemsCollection = afs.collection('items'); + this.items = this.itemsCollection.snapshotChanges(['added', 'removed']); +} +``` + +#### Component example + +```ts +import { Component } from '@angular/core'; +import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore'; +import { Observable } from 'rxjs'; + +@Component({ + selector: 'app-root', + template: `{%raw%} +
    +
  • + {{ item.name }} +
  • +
+ {%endraw%}` +}) +export class AppComponent { + private itemsCollection: AngularFirestoreCollection; + items: Observable; + constructor(private afs: AngularFirestore) { + this.itemsCollection = afs.collection('items'); + this.items = this.itemsCollection.valueChanges(['added', 'removed']); + } +} +``` + +## State based vs. action based + +Each one of these methods falls into two categories: state based and action based. State based methods return the state of your collection "as-is". Whereas action based methods return "what happened" in your collection. + +For example, a user updates the third item in a list. In a state based method like `.valueChanges()` will update the third item in the collection and return an array of JSON data. This is how your state looks. + +## Adding documents to a collection + +To add a new document to a collection with a generated id use the `add()` method. This method uses the type provided by the generic class to validate it's type structure. + +#### Basic example + +```ts +constructor(private afs: AngularFirestore): { + const shirtsCollection = afs.collection('tshirts'); + shirtsCollection.add({ name: 'item', price: 10 }); +} +``` + +## Manipulating individual documents + +To retrieve, update, or delete an individual document you can use the `doc()` method. This method returns an `AngularFirestoreDocument`, which provides methods for streaming, updating, and deleting. [See Using Documents with AngularFirestore for more information on how to use documents](/firestore/documents). + diff --git a/site/src/firestore/documents.md b/site/src/firestore/documents.md new file mode 100644 index 000000000..6639b3eeb --- /dev/null +++ b/site/src/firestore/documents.md @@ -0,0 +1,120 @@ +--- +title: Documents +eleventyNavigation: + key: Documents + parent: Firestore +--- + +## Documents in AngularFirestore + +Cloud Firestore is a NoSQL, document-oriented database. Unlike a SQL database, there are no tables or rows. Instead, you store data in *documents*, which are organized into *collections*. +Each *document* contains a set of key-value pairs. Cloud Firestore is optimized for storing large collections of small documents. + +## Using `AngularFirestoreDocument` + +The `AngularFirestoreDocument` service is a wrapper around the native Firestore SDK's [`DocumentReference` type](https://firebase.google.com/docs/reference/js/firebase.firestore.DocumentReference). It is a generic service that provides you with a strongly typed set of methods for manipulating and streaming data. This service is designed for use as an `@Injectable()`. + + + +```ts +import { Component } from '@angular/core'; +import { AngularFirestore, AngularFirestoreDocument } from '@angular/fire/firestore'; +import { Observable } from 'rxjs'; + +export interface Item { name: string; } + +@Component({ + selector: 'app-root', + template: `{% raw %} +
+ {{ (item | async)?.name }} +
+ {% endraw %}` +}) +export class AppComponent { + private itemDoc: AngularFirestoreDocument; + item: Observable; + constructor(private afs: AngularFirestore) { + this.itemDoc = afs.doc('items/1'); + this.item = this.itemDoc.valueChanges(); + } + update(item: Item) { + this.itemDoc.update(item); + } +} +``` + +## The `DocumentChangeAction` type + +With the exception of the `valueChanges()`, each streaming method returns an Observable of `DocumentChangeAction[]`. + +A `DocumentChangeAction` gives you the `type` and `payload` properties. The `type` tells when what `DocumentChangeType` operation occured (`added`, `modified`, `removed`). The `payload` property is a `DocumentChange` which provides you important metadata about the change and a `doc` property which is the `DocumentSnapshot`. + +```ts +interface DocumentChangeAction { + //'added' | 'modified' | 'removed'; + type: DocumentChangeType; + payload: DocumentChange; +} + +interface DocumentChange { + type: DocumentChangeType; + doc: DocumentSnapshot; + oldIndex: number; + newIndex: number; +} + +interface DocumentSnapshot { + exists: boolean; + ref: DocumentReference; + id: string; + metadata: SnapshotMetadata; + data(): DocumentData; + get(fieldPath: string): any; +} +``` + +## Streaming document data + +There are multiple ways of streaming collection data from Firestore. + +## `valueChanges({ idField?: string })` + +*What is it?* - Returns an Observable of document data. All Snapshot metadata is stripped. This method provides only the data. Optionally, you can pass an options object with an `idField` key containing a string. If provided, the returned object will include its document ID mapped to a property with the name provided by `idField`. + +*Why would you use it?* - When you just need the object data. No document metadata is attached which makes it simple to render to a view. + +*When would you not use it?* - When you need document metadata. + +## `snapshotChanges()` + +*What is it?* - Returns an Observable of data as a `DocumentChangeAction`. + +*Why would you use it?* - When you need the document data but also want to keep around metadata. This metadata provides you the underyling `DocumentReference` and document id. Having the document's id around makes it easier to use data manipulation methods. This method gives you more horsepower with other Angular integrations such as ngrx, forms, and animations due to the `type` property. The `type` property on each `DocumentChangeAction` is useful for ngrx reducers, form states, and animation states. + +*When would you not use it?* - When you simply need to render data to a view and don't want to do any extra processing. + +## Manipulating documents + +AngularFirestore provides methods for setting, updating, and deleting document data. + +- `set(data: T)` - Destructively updates a document's data. +- `update(data: T)` - Non-destructively updates a document's data. +- `delete()` - Deletes an entire document. Does not delete any nested collections. + +## Querying? + +Querying has no effect on documents. Documents are a single object and querying effects a range of multiple documents. If you are looking for querying then you want to use a collection. + +## Retrieving nested collections + +Nesting collections is a great way to structure your data. This allows you to group related data structures together. If you are creating a "Task List" site, you can group "tasks" under a user: `user//tasks`. + +To retrieve a nested collection use the `collection(path: string)` method. + +```ts +constructor(private afs: AngularFirestore) { + this.userDoc = afs.doc('user/david'); + this.tasks = this.userDoc.collection('tasks').valueChanges(); +} +``` diff --git a/site/src/firestore/firestore.11tydata.json b/site/src/firestore/firestore.11tydata.json new file mode 100644 index 000000000..b924da1f3 --- /dev/null +++ b/site/src/firestore/firestore.11tydata.json @@ -0,0 +1,4 @@ +{ + "layout": "guide.njk", + "tags": "guides" +} diff --git a/site/src/firestore/index.md b/site/src/firestore/index.md new file mode 100644 index 000000000..647cfb988 --- /dev/null +++ b/site/src/firestore/index.md @@ -0,0 +1,5 @@ +--- +eleventyNavigation: + key: Firestore + order: 2 +--- \ No newline at end of file diff --git a/site/src/functions/functions.11tydata.json b/site/src/functions/functions.11tydata.json new file mode 100644 index 000000000..b924da1f3 --- /dev/null +++ b/site/src/functions/functions.11tydata.json @@ -0,0 +1,4 @@ +{ + "layout": "guide.njk", + "tags": "guides" +} diff --git a/site/src/functions/getting-started.md b/site/src/functions/getting-started.md new file mode 100644 index 000000000..2ddf6f632 --- /dev/null +++ b/site/src/functions/getting-started.md @@ -0,0 +1,170 @@ +--- +title: Getting started +eleventyNavigation: + key: Getting started + parent: Functions +--- + +## Using AngularFireFunctions + +The Cloud Functions for Firebase client SDKs let you call functions directly from a Firebase app. To call a function from your app in this way, write and deploy an HTTPS Callable function in Cloud Functions, and then add client logic to call the function from your app. + +## Import the `NgModule` + +Cloud Functions for AngularFire is contained in the `@angular/fire/functions` module namespace. Import the `AngularFireFunctionsModule` in your `NgModule`. This sets up the `AngularFireFunction` service for dependency injection. + +```ts +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; +import { AppComponent } from './app.component'; +import { AngularFireModule } from '@angular/fire'; +import { AngularFireFunctionsModule } from '@angular/fire/functions'; +import { environment } from '../environments/environment'; + +@NgModule({ + imports: [ + BrowserModule, + AngularFireModule.initializeApp(environment.firebase), + AngularFireFunctionsModule + ], + declarations: [ AppComponent ], + bootstrap: [ AppComponent ] +}) +export class AppModule {} +``` + +## Injecting the `AngularFireFunctions` service + +Once the `AngularFireFunctionsModule` is registered you can inject the `AngularFireFunctions` service. + +```ts +import { Component } from '@angular/core'; +import { AngularFireFunctions } from '@angular/fire/functions'; + +@Component({ + selector: 'app-component', + template: `` +}) +export class AppComponent { + constructor(private fns: AngularFireFunctions) { } +} +``` + +## Creating a callable function + +AngularFireFunctions is super easy. You create a function on the server side and then "call" it by its name with the client library. + +| method | | +| ---------|--------------------| +| `httpCallable(name: string): (data: T) ` | Creates a callable function based on a function name. Returns a function that can create the observable of the http call. | +```ts + +import { Component } from '@angular/core'; +import { AngularFireFunctions } from '@angular/fire/functions'; + +@Component({ + selector: 'app-root', + template: `{ data$ | async }` +}) +export class AppComponent { + constructor(private fns: AngularFireFunctions) { + const callable = fns.httpsCallable('my-fn-name'); + this.data$ = callable({ name: 'some-data' }); + } +} +``` + +Notice that calling `httpsCallable()` does not initiate the request. It creates a function, which when called creates an Observable, subscribe or convert it to a Promise to initiate the request. + +## Configuration via Dependency Injection + +### Functions Region + +Allow configuration of the Function's region by adding `REGION` to the `providers` section of your `NgModule`. The default is `us-central1`. + +```ts +import { NgModule } from '@angular/core'; +import { AngularFireFunctionsModule, REGION } from '@angular/fire/functions'; + +@NgModule({ + imports: [ + ... + AngularFireFunctionsModule, + ... + ], + ... + providers: [ + { provide: REGION, useValue: 'asia-northeast1' } + ] +}) +export class AppModule {} + +``` + +### Cloud Functions emulator + +Point callable Functions to the Cloud Function emulator by adding `USE_EMULATOR` to the `providers` section of your `NgModule`. + +```ts +import { NgModule } from '@angular/core'; +import { AngularFireFunctionsModule, USE_EMULATOR } from '@angular/fire/functions'; + +@NgModule({ + imports: [ + ... + AngularFireFunctionsModule, + ... + ], + ... + providers: [ + { provide: USE_EMULATOR, useValue: ['localhost', 5001] } + ] +}) +export class AppModule {} + +``` + +[Learn more about integration with the Firebase Emulator suite on our dedicated guide here](../emulators/emulators.md). + +## Firebase Hosting integration + +If you serve your app using [Firebase Hosting](https://firebase.google.com/docs/hosting/), you can configure Functions to be served from the same domain as your app. This will avoid an extra round-trip per function call due to [CORS preflight request](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request). This only applies to sites hosted via firebase on `us-central1`. + +To set this up, you first need to update your `hosting` section in `firebase.json` and add one `rewrite` rule per function: + +```json + "hosting": { + "rewrites": [ + { + "source": "/someFunction", + "function": "someFunction" + }, + { + "source": "/anotherFunction", + "function": "anotherFunction" + }, + ... + ] + } +``` + +Deploy your hosting project to the new settings go into effect, finally configure functions origin to point at your app domain: + +```ts +import { NgModule } from '@angular/core'; +import { AngularFireFunctionsModule, ORIGIN, NEW_ORIGIN_BEHAVIOR } from '@angular/fire/functions'; + +@NgModule({ + imports: [ + ... + AngularFireFunctionsModule, + ... + ], + ... + providers: [ + { provide: NEW_ORIGIN_BEHAVIOR, useValue: true }, + { provide: ORIGIN, useValue: 'https://project-name.web.app' } + ] +}) +export class AppModule {} +``` diff --git a/site/src/functions/index.md b/site/src/functions/index.md new file mode 100644 index 000000000..0cc6ccdac --- /dev/null +++ b/site/src/functions/index.md @@ -0,0 +1,5 @@ +--- +eleventyNavigation: + key: Functions + order: 7 +--- diff --git a/site/src/get-started/deploying.md b/site/src/get-started/deploying.md new file mode 100644 index 000000000..566839053 --- /dev/null +++ b/site/src/get-started/deploying.md @@ -0,0 +1,152 @@ +--- +title: Deploying +eleventyNavigation: + key: Deploying + parent: Get started +--- + +## Static or Server-side rendered + +> **This page is out of date.** It describes a setup flow that `ng add @angular/fire` no longer performs, and wiring the deploy target by hand from the samples below puts you on a deprecated path that warns on every deploy. For current guidance see [the Quickstart](https://github.com/angular/angularfire/blob/main/docs/install-and-setup.md), step 6, and for server-side rendered apps see the [App Hosting guide](https://github.com/angular/angularfire/blob/main/docs/app-hosting.md). + +In this guide, we'll look at how to use `@angular/fire` to automatically deploy an Angular application to Firebase Hosting or Cloud Functions by using the Angular CLI. + +`@angular/fire` uses Firebase functions to deploy your Angular Universal projects, with server-side rendering enabled. + +**Angular Universal deployments work with `@nguniversal/*` version 9.0.0 and above**. + +## Add `@angular/fire` to your project + +First, you need to add the `@angular/fire` package to your project. In your Angular CLI project run: + +```shell +ng add @angular/fire +``` + +*Note that the command above assumes you have global Angular CLI installed. To install Angular CLI globally run `npm i -g @angular/cli`.* + +First, the command above will check if you have an Angular universal project. It'll do so by looking at your `angular.json` project, looking for a `server` target for the specified project. If it finds one, it'll ask you if you want to deploy the project in a firebase function. + +After that it will trigger the `@angular/fire` `ng-add` schematics. The schematics will open a web browser and guide you through the Firebase authentication flow (if you're not signed in already). After you authenticate, you'll see a prompt to select a Firebase hosting project. + +The schematics will do the following: + +1. Add `@angular/fire` to your list of dependencies +2. Create `firebase.json`, `.firebaserc` files in the root of your workspace. You can use them to configure your firebase hosting deployment. Find more about them [here](https://firebase.google.com/docs/hosting/full-config) +3. Update your workspace file (`angular.json`) by inserting the `deploy` builder + +In the end, your `angular.json` project will look like below: + +```json5 +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "sample-app": { + // ... + "deploy": { + "builder": "@angular/fire:deploy", + "options": {} // Here you may find an "ssr": true option if you've + // selected that you want to deploy your Angular universal project + // as a firebase function. + } + } + }, + // ... + "defaultProject": "sample-app" + +} +``` + +If you want to add deployment capabilities to a different project in your workspace, you can run: + +```bash +ng add @angular/fire --project=[PROJECT_NAME] +``` + +## Deploying the project + +As the second step, to deploy your project run: + +```bash +ng deploy --project=[PROJECT_NAME] +``` + +*The `--project` option is optional. Learn more [here](https://angular.io/cli/deploy).* + +The command above will trigger: + +1. Production build of your application +2. Deployment of the produced assets to the firebase hosting project you selected during `ng add` + +If you've specified that you want a server-side rendering enabled deployment in a firebase function, the command will also: + +1. Create a firebase function in `dist`, which directly consumes `main.js` from your server output directory. +2. Create `package.json` for the firebase function with the required dependencies. +3. Deploy the static assets to firebase hosting and your universal server as a Firebase function. + +If you want to preview your Angular Universal project before we deploy it as a Firebase Function you can run: + +``` +ng deploy --preview +``` + +We'll create the function and a `package.json` in your project output directory. This way, you can later run `firebase serve` in your project root so you can test everything before deploying. + +## Customization + +To customize the deployment flow, you can use the configuration files you're already familiar with from `firebase-tools`. You can find more in the [firebase documentation](https://firebase.google.com/docs/hosting/full-config). + +### Configuring Cloud Functions + +Setting `functionsNodeVersion` and `functionsRuntimeOptions` in your `angular.json` allow you to customize the version of Node.js Cloud Functions is running and run-time settings like timeout, VPC connectors, and memory. + +```json +"deploy": { + "builder": "@angular/fire:deploy", + "options": { + "functionsNodeVersion": 22, + "functionsRuntimeOptions": { + "memory": "2GB", + "timeoutSeconds": 10, + "vpcConnector": "my-vpc-connector", + "vpcConnectorEgressSettings": "PRIVATE_RANGES_ONLY" + } + } +} +``` + +### Working with multiple Firebase Projects + +If you have multiple build targets and deploy targets, it is possible to specify them in your `angular.json` or `workspace.json`. + +It is possible to use either your project name or project alias in `firebaseProject`. The setting provided here is equivalent to passing a project name or alias to `firebase deploy --project projectNameOrAlias`. + +The `buildTarget` simply points to an existing build configuration for your project. Most projects have a default configuration and a production configuration (commonly activated by using the `--prod` flag) but it is possible to specify as many build configurations as needed. + +You may specify a `buildTarget` and `firebaseProject` in your `options` as follows: + +```json +"deploy": { + "builder": "@angular/fire:deploy", + "options": { + "buildTarget": "projectName:build", + "firebaseProject": "developmentProject" + }, + "configurations": { + "production": { + "buildTarget": "projectName:build:production", + "firebaseProject": "productionProject" + } + } +} +``` + +The above configuration specifies the following: + +1. `ng deploy` will deploy the default project with default configuration. +2. `ng deploy projectName` will deploy the specified project with default configuration. +3. `ng deploy projectName --prod` or `ng deploy projectName --configuration='production'` will deploy `projectName` with production build settings to your production environment. + +All of the options are optional. If you do not specify a `buildTarget`, it defaults to a production build (`projectName:build:production`). If you do not specify a `firebaseProject`, it defaults to the first matching deploy target found in your `.firebaserc` (where your projectName is the same as your Firebase deploy target name). The `configurations` section is also optional. diff --git a/site/src/get-started/get-started.11tydata.json b/site/src/get-started/get-started.11tydata.json new file mode 100644 index 000000000..b924da1f3 --- /dev/null +++ b/site/src/get-started/get-started.11tydata.json @@ -0,0 +1,4 @@ +{ + "layout": "guide.njk", + "tags": "guides" +} diff --git a/site/src/get-started/index.md b/site/src/get-started/index.md new file mode 100644 index 000000000..0485183af --- /dev/null +++ b/site/src/get-started/index.md @@ -0,0 +1,5 @@ +--- +eleventyNavigation: + key: Get started + order: 1 +--- diff --git a/site/src/get-started/local-development.md b/site/src/get-started/local-development.md new file mode 100644 index 000000000..b9d986a81 --- /dev/null +++ b/site/src/get-started/local-development.md @@ -0,0 +1,135 @@ +--- +title: Local development +eleventyNavigation: + key: Local development + parent: Get started +--- + +## Connect to the Firebase Emulator Suite + +In this guide, we'll look at how to use `@angular/fire` to connect an Angular application with the Firebase Emulator Suite to start prototyping your apps. + +There are four supported emulators, all of them available at the Firebase suite workflow: + +- [Authentication Emulator](https://firebase.google.com/docs/emulator-suite/connect_auth) +- [Realtime Database Emulator](https://firebase.google.com/docs/emulator-suite/connect_rtdb) +- [Cloud Firestore Emulator](https://firebase.google.com/docs/emulator-suite/connect_firestore) +- [Cloud Functions Emulator](https://firebase.google.com/docs/emulator-suite/connect_functions) + +*The Auth Emulator only works with Firebase v8 and above, which is supported by `@angular/fire` 6.1.0 or higher*. + +Before configuring these emulators at the Angular App, be sure to install the ones you need by following the [Install, configure and integrate Local Emulator Suite](https://firebase.google.com/docs/emulator-suite/install_and_configure) documentation. + +Initialize firebase to your project: + +```shell +firebase init +``` + +Then launch the emulator setup wizard: + +```shell +firebase init emulators +``` + +Follow the instructions to download whatever emulator you want to use then checkout that the `firebase.json` file got updated with the default ports per emulator, something like this: + +```json +{ + // Existing firebase configuration ... + // Optional emulator configuration. Default + // values are used if absent. + "emulators": { + "firestore": { + "port": "8080" + }, + "ui": { + "enabled": true, // Default is `true` + "port": 4000 // If unspecified, see CLI log for selected port + }, + "auth": { + "port": "9099" + }, + "functions": { + "port": "5001" + }, + "database": { + "port": "9000" + }, + "pubsub": { + "port": "8085" + } + } +} +``` + +## Import the DI Tokens at your AppModule + +Configuring your app to connect to local emulators is easily done by using dependency injection tokens provided by the library. However, there are slighty changes between 6.0.0 and 6.1.0 in the way it was done. + +Each module (database, firestore, auth, function) provides `USE_EMULATOR` token to configure the emulator `host` and `port` by passing a tuple of `[string, number]` values, which are set by default to `localhost` and the asigned port from your `firebase.json` file. + +Import these tokens at your `app.module.ts` as follow: + +```ts +import { USE_EMULATOR as USE_AUTH_EMULATOR } from '@angular/fire/auth'; +import { USE_EMULATOR as USE_DATABASE_EMULATOR } from '@angular/fire/database'; +import { USE_EMULATOR as USE_FIRESTORE_EMULATOR } from '@angular/fire/firestore'; +import { USE_EMULATOR as USE_FUNCTIONS_EMULATOR } from '@angular/fire/functions'; + +@NgModule({ + // ... Existing configuration + providers: [ + // ... Existing Providers + { provide: USE_AUTH_EMULATOR, useValue: environment.useEmulators ? ['localhost', 9099] : undefined }, + { provide: USE_DATABASE_EMULATOR, useValue: environment.useEmulators ? ['localhost', 9000] : undefined }, + { provide: USE_FIRESTORE_EMULATOR, useValue: environment.useEmulators ? ['localhost', 8080] : undefined }, + { provide: USE_FUNCTIONS_EMULATOR, useValue: environment.useEmulators ? ['localhost', 5001] : undefined }, + ] +}) +export class AppModule { } +``` + +The environment `useEmulators` flag is used to control whenever the app should connect to the emulators, which is usually done in non-production environments. + +Also you can opt-in the new way of setting the Cloud Functions [origin](https://firebase.google.com/docs/functions/locations) in Firebase v8 by using the `NEW_ORIGIN_BEHAVIOR` token in conjuction with the already present `ORIGIN` token. + +```ts +import { isDevMode, NgModule } from '@angular/core'; +import { ORIGIN as FUNCTIONS_ORIGIN, NEW_ORIGIN_BEHAVIOR } from '@angular/fire/functions'; + +@NgModule({ + // ... Existing configuration + providers: [ + // ... Existing Providers + { provide: NEW_ORIGIN_BEHAVIOR, useValue: true }, + { provide: FUNCTIONS_ORIGIN, useFactory: () => isDevMode() ? undefined : location.origin }, + ] +}) +export class AppModule { } +``` + +## Older method (6.0.0) + +With the exception of the Auth Emulator, the old way of setting the `host` and `port` for each emulator was done using a different set of tokens by passing the entire url path as string. + +```ts +import { URL as DATABASE_URL } from '@angular/fire/database'; +import { ORIGIN as FUNCTIONS_ORIGIN } from '@angular/fire/functions'; +import { SETTINGS as FIRESTORE_SETTINGS } from '@angular/fire/firestore'; + +@NgModule({ + // ... Existing configuration + providers: [ + { + provide: DATABASE_URL, + useValue: environment.useEmulators ? `http://localhost:9000?ns=${environment.firebase.projectId}` : undefined + }, + { provide: FIRESTORE_SETTINGS, useValue: environment.useEmulators ? { host: 'localhost:8080', ssl: false } : {} }, + { provide: FUNCTIONS_ORIGIN, useFactory: environment.useEmulators ? 'http://localhost:5001' : undefined }, + ] +}) +export class AppModule { } +``` + +For older versions, please upgrade your app to latest version to get the advantages of these new features :rocket: diff --git a/site/src/get-started/quick-start.md b/site/src/get-started/quick-start.md new file mode 100644 index 000000000..9fd8a19a6 --- /dev/null +++ b/site/src/get-started/quick-start.md @@ -0,0 +1,212 @@ +--- +title: Quick start +eleventyNavigation: + key: Quick start + parent: Get started +--- + +## Create a new project + +```bash +npm install -g @angular/cli +ng new +cd +``` + +The Angular CLI's `new` command will set up the latest Angular build in a new project structure. + +## Install AngularFire and Firebase + +```bash +ng add @angular/fire +``` + +Now that you have a new project setup, install AngularFire and Firebase from npm. + +## Add Firebase config to environments variable + +Open `/src/environments/environment.ts`, create it if it doesn't exist, and add your Firebase configuration. You can find your project configuration in [the Firebase Console](https://console.firebase.google.com). Click the Gear icon next to Project Overview, in the Your Apps section, create a new app and choose the type Web. Give the app a name and copy the config values provided. + +```ts +export const environment = { + production: false, + firebase: { + apiKey: '', + authDomain: '', + databaseURL: '', + projectId: '', + storageBucket: '', + messagingSenderId: '', + appId: '', + measurementId: '' + } +}; +``` + +## Configuring Firebase in your Angular Application + +### Set up Firebase for `@NgModule` based apps +For applications bootstrapped using `@NgModule`, add the Firebase providers to the imports array of the `@NgModule` decorator metadata. + +In `/src/app/app.module.ts` update the code to:. + +```ts +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; +import { AppComponent } from './app.component'; +import { initializeApp, provideFirebaseApp } from '@angular/fire/app'; +import { environment } from '../environments/environment'; + +@NgModule({ + imports: [ + BrowserModule, + provideFirebaseApp(() => initializeApp(environment.firebase)), + ], + declarations: [ AppComponent ], + bootstrap: [ AppComponent ] +}) +export class AppModule {} +``` +### Set up Firebase for Standalone API based apps +Beginning in [Angular v14](https://blog.angular.io/angular-v14-is-now-available-391a6db736af) applications can be built and bootstrapped using the set of [Standalone APIs](https://angular.io/guide/standalone-components). + +The provider configuration for these applications should be added to the `bootstrapApplication` function in `main.ts`: + +```ts +import { initializeApp, provideFirebaseApp } from '@angular/fire/app'; +import { getAuth, provideAuth } from '@angular/fire/auth'; +import { bootstrapApplication } from '@angular/platform-browser'; +import { AppComponent } from './app/app.component'; +import { environment } from './environments/environment'; + + +bootstrapApplication(AppComponent, { + providers: [ + provideFirebaseApp(()=> initializeApp(environment.firebase)) + ], +}).catch(err => console.error(err)); +``` + +### Configuring Firebase features + +After adding the Firbase app providers, you also need to add providers for the each of Firebase features your application needs. + +For example if your application uses both Google Analytics and the Firestore database you would add `provideAnalytics` and `provideFirestore`: + +```ts +// Module based configuration app.module.ts + +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; +import { AppComponent } from './app.component'; +import { initializeApp, provideFirebaseApp } from '@angular/fire/app'; +import { provideAnalytics, getAnalytics } from '@angular/fire/analytics'; +import { provideFirestore, getFirestore } from '@angular/fire/firestore'; +import { environment } from '../environments/environment'; + +@NgModule({ + imports: [ + BrowserModule, + provideFirebaseApp(() => initializeApp(environment.firebase)), + provideAnalytics(()=> getAnalytics()), + provideFirestore(() => getFirestore()), + AngularFireAnalyticsModule, + AngularFirestoreModule + ], + declarations: [ AppComponent ], + bootstrap: [ AppComponent ] +}) +export class AppModule {} +``` + +```ts +// Standalone API based config (main.ts) +import { initializeApp, provideFirebaseApp } from '@angular/fire/app'; +import { provideFirestore, getFirestore } from '@angular/fire/firestore' +import { getAuth, provideAuth } from '@angular/fire/auth'; +import { bootstrapApplication } from '@angular/platform-browser'; +import { AppComponent } from './app/app.component'; +import { environment } from './environments/environment'; + + +bootstrapApplication(AppComponent, { + providers: [ + provideFirebaseApp(() => initializeApp(environment.firebase)), + provideAuth(() => getAuth()), + provideFirestore(() => getFirestore()) + ], +}).catch(err => console.error(err)); +``` +## Inject `AngularFirestore` + +Open `/src/app/app.component.ts`, and make sure to modify/delete any tests to get the sample working (tests are still important, you know): + +```ts +import { Component, inject } from '@angular/core'; +import { Firestore } from '@angular/fire/firestore'; + +@Component({ + selector: 'app-root', + templateUrl: 'app.component.html', + styleUrls: ['app.component.css'] +}) +export class AppComponent { + private firestore: AngularFirestore = inject(Firestore) + ... +} +``` + +## Bind a Firestore collection to a list + +In `/src/app/app.component.ts`: + +```ts +import { Component } from '@angular/core'; +import { Firestore, collection } from '@angular/fire/firestore'; +import { Observable } from 'rxjs'; + +@Component({ + selector: 'app-root', + templateUrl: 'app.component.html', + styleUrls: ['app.component.css'] +}) +export class AppComponent { + private firestore: Firestore = inject(Firestore); + items$: Observable; + + constructor() { + const itemsCollectionRef = collection(this.firestore, 'items'); + this.items = collectionData(itemsCollectionRef) as Observable; + } +} +``` + +Open `/src/app/app.component.html`: + +```html +
    +
  • + {{item.name}} +
  • +
+``` + +## Run your app locally + +```bash +ng serve +``` + +Your Angular app will compile and serve locally, visit it we should see an empty list. + +In another tab [start adding data to an `items` collection in Firestore](https://firebase.google.com/docs/firestore/manage-data/add-data). *As we're not authenticating users yet, be sure to start Firestore in **test mode** or allow reading from the `items` collection in Security Rules (`allow read: if true`).* + +Once you've created a `items` collection and are inserting documents, you should see data streaming into your Angular application. + +## Deploy your app + +Finally, we can deploy the application to Firebase hosting: + +```bash +ng deploy +``` \ No newline at end of file diff --git a/site/src/index.md b/site/src/index.md new file mode 100644 index 000000000..f75605062 --- /dev/null +++ b/site/src/index.md @@ -0,0 +1,50 @@ +--- +layout: default.njk +--- + +{% headingone %}AngularFire{% endheadingone %} + +{% subheading %}The official library for Angular and Firebase{% endsubheading %} + +
+
+ {%- linkbutton "/get-started/quick-start" %} + Get started + {%- endlinkbutton %} +
+
+ {%- linkbutton "https://github.com/angular/fire", "secondary", true %} + GitHub + {%- endlinkbutton %} +
+
+ +{% disclaimerprod %} + +## What is AngularFire? + +AngularFire smooths over the rough edges an Angular developer might encounter when implementing the framework-agnostic Firebase JS SDK & aims to provide a more natural developer experience by conforming to Angular conventions. + +### Dependency injection +Provide and Inject Firebase services in your components + +### Zone.js wrappers +Stable zones allow proper functionality of service workers, forms, SSR, and pre-rendering + +### Observable based +Utilize RxJS rather than callbacks for realtime streams + +### NgRx friendly API +Integrate with NgRx using AngularFire's action based APIs. + +### Lazy-loading +AngularFire dynamically imports much of Firebase, reducing time to load your app + +### Deploy schematics +Get your Angular application deployed on Firebase Hosting with a single command + +### Google Analytics +Zero-effort Angular Router awareness in Google Analytics + +### Router Guards +Guard your Angular routes with built-in Firebase Authentication checks diff --git a/docs/ionic/authentication.md b/site/src/ionic/authentication.md similarity index 64% rename from docs/ionic/authentication.md rename to site/src/ionic/authentication.md index d0c8c822a..a42a946e4 100644 --- a/docs/ionic/authentication.md +++ b/site/src/ionic/authentication.md @@ -1,4 +1,14 @@ -# Step 1: install this plugin: +--- +title: Authentication +eleventyNavigation: + key: Authentication + parent: Ionic +--- + +## Setting up Ionic and Firebase Auth + +First start out by installing the needed plugins below. + ``` ionic cordova plugin add cordova-universal-links-plugin ionic cordova plugin add cordova-plugin-buildinfo @@ -8,26 +18,23 @@ ionic cordova plugin add cordova-plugin-inappbrowser ionic cordova plugin add cordova-plugin-customurlscheme ``` -# Step 2: Add Firebase to your Ionic - - **Install Firebase to Angular project** +## Add Firebase to your Ionic app Follow [this tutorial](https://github.com/angular/angularfire2/blob/master/docs/install-and-setup.md) to make a basic setup for your Ionic project. - **To set up in an Android app** +### To set up in an Android app -Go to [Firebase console](https://console.firebase.google.com/) then click **Add Firebase to your Android app** and follow the setup steps. +Go to [Firebase console](https://console.firebase.google.com/) then click *Add Firebase to your Android app* and follow the setup steps. - -# Step 3: Set up Firebase Authentication for Cordova +## Set up Firebase Authentication for Cordova *This is a summary from the [Firebase instructions](https://firebase.google.com/docs/auth/web/cordova).* - **Setup Dynamic Link** - In the Firebase console, open the **Dynamic Links** section at bottom left panel, setup by their instruction +### Setup Dynamic Link +In the Firebase console, open the *Dynamic Links* section at bottom left panel, setup by their instruction + +Add this to config.xml at root level of project: - **Add dynamic link** - *Add this to config.xml at root level of project:* ```xml @@ -40,12 +47,12 @@ Go to [Firebase console](https://console.firebase.google.com/) then click **Add ``` +Make sure your `` the same with Android app's id you added in Firebase. - *Make sure your `` the same with Android app's id you - added in Firebase.* +## Add login code -# Step 4: Add login code In `login.service.ts` add this function: + ```ts import { AngularFireAuth } from '@angular/fire/auth'; @@ -91,14 +98,3 @@ export class AuthService { } } ``` - -# Common problems - -If you get an error, when you build the code, that looks like this: -``` -UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: Cannot read property 'manifest' of undefined -``` - -Please take a look at the fix from [this issue](https://github.com/nordnet/cordova-universal-links-plugin/issues/134): -> Making this change in 'cordova-universal-links-plugin/hooks/lib/android/manifestWriter.js' fixed this issue for me: -> [b2c5784#diff-d5955d9f4d88b42e5efd7a3385be79e9](https://github.com/nordnet/cordova-universal-links-plugin/commit/b2c5784764225319648e26aa5d3f42ede6d1b289#diff-d5955d9f4d88b42e5efd7a3385be79e9) diff --git a/docs/ionic/cli.md b/site/src/ionic/getting-started.md similarity index 68% rename from docs/ionic/cli.md rename to site/src/ionic/getting-started.md index a0c41ae7a..a558072c7 100644 --- a/docs/ionic/cli.md +++ b/site/src/ionic/getting-started.md @@ -1,9 +1,13 @@ -# Installation and Setup with Ionic CLI +--- +title: Getting started +eleventyNavigation: + key: Getting started + parent: Ionic +--- -### 0. Prerequisites +## Setup with Ionic CLI -Before you start installing AngularFire, make sure you have latest version of Ionic cli installed. -To verify run the command `ionic -v` and check your version. The CLI should be at least version 3.0.0 or greater. +Before you start installing AngularFire, make sure you have latest version of Ionic cli installed. To verify run the command `ionic -v` and check your version. The CLI should be at least version 3.0.0 or greater. If not, you may need to do the following: @@ -16,7 +20,7 @@ npm cache clean npm install -g @ionic/cli ``` -### 1. Create a new project +## Create a new project ```bash ionic start @@ -25,7 +29,7 @@ cd The Ionic CLI's `start` command will prompt you to pick a starting template, and scaffold out the project for you. -### 2. Test your Setup +## Test your Setup ```bash ionic serve @@ -33,7 +37,7 @@ ionic serve Your default browser should start up and display a working Ionic app. -### 3. Install AngularFire 2 and Firebase +## Install AngularFire & Firebase ```bash npm install @angular/fire firebase --save @@ -41,7 +45,7 @@ npm install @angular/fire firebase --save Now that you have a new project setup, install AngularFire and Firebase from npm. -### 4. Add Firebase config to environments variable +### Add Firebase config to environments variable Let's create a new file, `src/environment.ts` and start adding our Firebase config: @@ -54,14 +58,12 @@ export const firebaseConfig = { storageBucket: '', messagingSenderId: '' }; - ``` -### 5. Setup @NgModule for the AngularFireModule +## Add the `AngularFireModule` -Open `/src/app/app.module.ts`, inject the Firebase providers, and specify your Firebase configuration. -This can be found in your project at [the Firebase Console](https://console.firebase.google.com): +Open `/src/app/app.module.ts`, inject the Firebase providers, and specify your Firebase configuration. This can be found in your project at [the Firebase Console](https://console.firebase.google.com): ```ts import { BrowserModule } from '@angular/platform-browser'; @@ -87,8 +89,7 @@ export class AppModule {} There will be more or less imports depending on your app. This is just an example setup. - -#### Custom FirebaseApp Names +## Custom FirebaseApp Names You can optionally provide a custom FirebaseApp name with `initializeApp`. ```ts @@ -104,19 +105,9 @@ You can optionally provide a custom FirebaseApp name with `initializeApp`. export class AppModule {} ``` -### 6. Setup individual @NgModules - -After adding the AngularFireModule you also need to add modules for the individual @NgModules that your application needs. - - AngularFireAuthModule - - AngularFireDatabaseModule - - AngularFireFunctionsModule - - AngularFirestoreModule - - AngularFireStorageModule - - AngularFireMessagingModule - -#### Adding the Firebase Database and Auth Modules +## Adding Feature Modules -For example if you application was using both Firebase authentication and the Firebase database you would add: +If your application was using both Firebase Auth and the Realtime Database you would add the following modules. ```ts import { BrowserModule } from '@angular/platform-browser'; @@ -134,19 +125,22 @@ import { AngularFireAuthModule } from '@angular/fire/auth'; declarations: [ MyApp ], imports: [ BrowserModule, + // imports firebase/app needed for everything + AngularFireModule.initializeApp(firebaseConfig), + // imports firebase/database, only needed for database features + AngularFireDatabaseModule, + // imports firebase/auth, only needed for auth features + AngularFireAuthModule, IonicModule.forRoot(MyApp), - AngularFireModule.initializeApp(firebaseConfig), // imports firebase/app needed for everything - AngularFireDatabaseModule, // imports firebase/database, only needed for database features - AngularFireAuthModule, // imports firebase/auth, only needed for auth features ], bootstrap: [IonicApp], }) ``` -### 7. Inject AngularFireDatabase +### Inject AngularFireDatabase -Open `/src/pages/home/home.ts`, and start to import `AngularFireDatabase` and `FirebaseListObservable`: +Open `/src/pages/home/home.ts`, and start to import `AngularFireDatabase`. ```ts import { Component } from '@angular/core'; @@ -180,7 +174,16 @@ import { Observable } from 'rxjs/Observable'; @Component({ selector: 'page-home', - templateUrl: 'home.html' + templateUrl: `{%raw%} + + --- + + + + + {{item | json}} + +{%endraw%}` }) export class HomePage { items: Observable; @@ -194,25 +197,3 @@ export class HomePage { } ``` -Open `/src/pages/home/home.html`: - -```html - - --- - - - - - {{item | json}} - - -``` - -### 9. Run your app - -```bash -ionic serve -``` - -That's it! If there's any issues, be sure to file an issue on the AngularFire repo or the Ionic repo, depending on who's to blame :smile: - diff --git a/site/src/ionic/index.md b/site/src/ionic/index.md new file mode 100644 index 000000000..3bc76eaaf --- /dev/null +++ b/site/src/ionic/index.md @@ -0,0 +1,6 @@ +--- +eleventyNavigation: + key: Ionic + order: 12 +--- + diff --git a/site/src/ionic/ionic.11tydata.json b/site/src/ionic/ionic.11tydata.json new file mode 100644 index 000000000..b924da1f3 --- /dev/null +++ b/site/src/ionic/ionic.11tydata.json @@ -0,0 +1,4 @@ +{ + "layout": "guide.njk", + "tags": "guides" +} diff --git a/site/src/js/click-card.js b/site/src/js/click-card.js new file mode 100644 index 000000000..66a2587ac --- /dev/null +++ b/site/src/js/click-card.js @@ -0,0 +1,17 @@ +customElements.define('eap-click-card', class extends HTMLElement { + connectedCallback() { + let down; + let up; + // Enhance to a pointer only if the JavaScript applies + this.style.cursor = 'pointer'; + // Note: This only works for a single link or the first one. + const firstOrOnlyLink = this.querySelector('a'); + this.onmousedown = () => down = +new Date(); + this.onmouseup = () => { + up = +new Date(); + if ((up - down) < 200) { + firstOrOnlyLink.click(); + } + } + } +}); \ No newline at end of file diff --git a/site/src/js/menu-button.js b/site/src/js/menu-button.js new file mode 100644 index 000000000..47018438f --- /dev/null +++ b/site/src/js/menu-button.js @@ -0,0 +1,13 @@ +customElements.define('eap-menu-button', class extends HTMLElement { + connectedCallback() { + const menuId = this.getAttribute('data-menu-id'); + const menuEl = document.getElementById(menuId); + const button = document.createElement('button'); + button.classList.add('fixed', 'w-16', 'h-16', 'text-white', 'rounded-full', 'shadow-lg', 'bottom-6', 'right-6', 'bg-grey-700', 'focus:ring-grey-600' , 'z-50', 'focus:ring-4', 'md:hidden', 'lg:hidden', 'xl:hidden'); + button.textContent = 'đŸ”Ĩ'; + this.appendChild(button); + button.addEventListener('click', clickEvent => { + menuEl.classList.toggle('slideIn'); + }); + } +}); diff --git a/site/src/js/tab-switcher.js b/site/src/js/tab-switcher.js new file mode 100644 index 000000000..5c6a03a68 --- /dev/null +++ b/site/src/js/tab-switcher.js @@ -0,0 +1,25 @@ +customElements.define('eap-tab-switcher', class extends HTMLElement {}); +customElements.define('eap-tab-list', class extends HTMLElement { + connectedCallback() { + this.buttonTabs = this.querySelectorAll('button'); + for(let button of this.buttonTabs) { + button.addEventListener('click', clickEvent => { + const activeButton = this.querySelector('button[aria-selected="true"]'); + const activePanelId = activeButton.dataset.panel; + const panelToDisplayId = button.dataset.panel; + const panelToDisplay = document.querySelector(`#${panelToDisplayId}`); + const activePanel = document.querySelector(`#${activePanelId}`); + if(activeButton.id !== button.id) { + button.setAttribute('aria-selected', true); + activeButton.setAttribute('aria-selected', false); + panelToDisplay.classList.add('block'); + panelToDisplay.classList.remove('hidden'); + activePanel.classList.remove('block'); + activePanel.classList.add('hidden'); + } + }); + } + } +}); +customElements.define('eap-tab-panel-list', class extends HTMLElement {}); +customElements.define('eap-tab-panel', class extends HTMLElement { }); \ No newline at end of file diff --git a/docs/messaging/messaging.md b/site/src/messaging/getting-started.md similarity index 78% rename from docs/messaging/messaging.md rename to site/src/messaging/getting-started.md index 65d3dfd71..b69a5ca80 100644 --- a/docs/messaging/messaging.md +++ b/site/src/messaging/getting-started.md @@ -1,17 +1,24 @@ -# AngularFireMessaging +--- +title: Getting started +eleventyNavigation: + key: Getting started + parent: Messaging +--- -> The FCM JavaScript API lets you receive notification messages in web apps running in browsers that support the Push API. +## Using AngularFireMessaging -### AngularFireMessaging is not compatible with the Angular Service Worker +The FCM JavaScript API lets you receive notification messages in web apps running in browsers that support the Push API. -If you are using the Angular Service Worker, you are not currently able to use AngularFireMessaging. -If you'd like this feature please add your 👍 to [this issue](https://github.com/angular/angular/issues/34352). +## Not readily compatible with the Angular Service Worker + +If you are using the Angular Service Worker, you are not currently able to use AngularFireMessaging out-of-the-box. If you'd like this feature please add your 👍 to [this issue](https://github.com/angular/angular/issues/34352). Your alternatives are to use -- [WorkboxJS](https://developers.google.com/web/tools/workbox/) +- [WorkboxJS](https://developers.google.com/web/tools/workbox/){.text-blue .underline} +- Follow the discussion in [this issue](https://github.com/angular/angular/issues/34352){.text-blue .underline} and [here](https://github.com/angular/angularfire/discussions/1923){.text-blue .underline}, manually registering the Angular Service Worker - The Firebase Messaging Service Worker, which is detailed below -### Import the `NgModule` +## Import the `NgModule` Push Notifications for AngularFire are contained in the `@angular/fire/messaging` module namespace. Import the `AngularFireMessagingModule` in your `NgModule`. This sets up the `AngularFireMessaging` service for dependency injection. @@ -50,6 +57,32 @@ You can either use the `firebase-messaging-sw.js` file provided in the docs or y ], ``` +[Warning] Remember update the `firebase-messaging-sw.js` everytime you update the `firebase` in package.json. The missmatch version could lead to unable to receive notification in `foreground`, you can create your `firebase-messaging-sw.js` like this: + +```js +// Give the service worker access to Firebase Messaging. +// Note that you can only use Firebase Messaging here, other Firebase libraries +// are not available in the service worker. +importScripts('https://www.gstatic.com/firebasejs/[the number of version matching with firebase in package.json]/firebase-app.js'); +importScripts('https://www.gstatic.com/firebasejs/[for example: 8.2.6]/firebase-messaging.js'); + +// Initialize the Firebase app in the service worker by passing in the +// messagingSenderId. + +firebase.initializeApp({ + apiKey: '', + authDomain: '', + databaseURL: '', + projectId: '', + storageBucket: '', + messagingSenderId: '' +}); + +// Retrieve an instance of Firebase Messaging so that it can handle background +// messages. +const messaging = firebase.messaging(); +``` + ### Requesting permission Once you have the Firebase Messaging Service Worker set up and installed, you need to request permission to send a user notifications. While the browser will popup a UI for you, it is highly recommend to ask the user for permission with a custom UI and only ask when it makes sense. If you blindly ask for permission, you have an extremely high chance of getting denied or blocked. @@ -198,4 +231,4 @@ export class AppComponent { [Sending a notification](https://firebase.google.com/docs/cloud-messaging/js/first-message) requires a call to a server. You can do this directly with an HTTP call or you can even build a Cloud Function to do this in response to an event. A Cloud Function trigger is ideal because you have trusted access to the database and can securely look up tokens to send to the right user. If you want to send push notifications via HTTP requests you'll need to secure the API call. This is usually done with a Firebase Auth UID. On the server you can verify the UID with the Firebase Admin SDK and allow access to get a user's push id. -The [Firebase Admin SDK has helper functions for sending notifications](https://firebase.google.com/docs/cloud-messaging/admin/send-messages) to the user and subscribing them to topics, which [simplifies sending grouped messages](https://firebase.google.com/docs/cloud-messaging/admin/manage-topic-subscriptions). +The [Firebase Admin SDK has helper functions for sending notifications](https://firebase.google.com/docs/cloud-messaging/admin/send-messages) to the user and subscribing them to topics, which [simplifies sending grouped messages](https://firebase.google.com/docs/cloud-messaging/admin/manage-topic-subscriptions). \ No newline at end of file diff --git a/site/src/messaging/index.md b/site/src/messaging/index.md new file mode 100644 index 000000000..8bf754c30 --- /dev/null +++ b/site/src/messaging/index.md @@ -0,0 +1,6 @@ +--- +eleventyNavigation: + key: Messaging + order: 8 +--- + diff --git a/site/src/messaging/messaging.11tydata.json b/site/src/messaging/messaging.11tydata.json new file mode 100644 index 000000000..b924da1f3 --- /dev/null +++ b/site/src/messaging/messaging.11tydata.json @@ -0,0 +1,4 @@ +{ + "layout": "guide.njk", + "tags": "guides" +} diff --git a/site/src/performance/getting-started.md b/site/src/performance/getting-started.md new file mode 100644 index 000000000..5de106ca2 --- /dev/null +++ b/site/src/performance/getting-started.md @@ -0,0 +1,131 @@ +--- +title: Getting started +eleventyNavigation: + key: Getting started + parent: Performance +--- + +## Automatic page load tracing + +Understand your Angular application's real-world performance with [Firebase Performance Monitoring](https://firebase.google.com/docs/perf-mon). Performance Monitoring automatically provides a trace for **page load** when you add `AngularFirePerformanceModule` into your App Module's imports. + +```ts +import { AngularFireModule } from '@angular/fire'; +import { AngularFirePerformanceModule, PerformanceMonitoringService } from '@angular/fire/performance'; +import { environment } from '../environments/environment'; + +@NgModule({ + imports: [ + BrowserModule, + AngularFireModule.initializeApp(environment.firebase), + AngularFirePerformanceModule, + ... + ], + providers: [ + PerformanceMonitoringService + ], + declarations: [ AppComponent ], + bootstrap: [ AppComponent ] +}) +export class AppModule {} +``` + +The page load trace breaks down into the following default metrics: + +* [First paint traces](https://firebase.google.com/docs/perf-mon/automatic-web#first-paint){.text-blue .underline} — measure the time between when the user navigates to a page and when any visual change happens +* [First contentful paint traces](https://firebase.google.com/docs/perf-mon/automatic-web#contentful-paint){.text-blue .underline} — measure the time between when a user navigates to a page and when meaningful content displays, like an image or text +* [domInteractive traces](https://firebase.google.com/docs/perf-mon/automatic-web#domInteractive){.text-blue .underline} — measure the time between when the user navigates to a page and when the page is considered interactive for the user +* [domContentLoadedEventEnd traces](https://firebase.google.com/docs/perf-mon/automatic-web#domContentLoaded){.text-blue .underline} — measure the time between when the user navigates to a page and when the initial HTML document is completely loaded and parsed +* [loadEventEnd traces](https://firebase.google.com/docs/perf-mon/automatic-web#loadEventEnd){.text-blue .underline} — measure the time between when the user navigates to the page and when the current document's load event completes +* [First input delay traces](https://firebase.google.com/docs/perf-mon/automatic-web#input-delay){.text-blue .underline} — measure the time between when the user interacts with a page and when the browser is able to respond to that input +* *Angular specific traces* - `PerformanceMonitoringService` will measure the time needed for `ApplicationRef.isStable` to be true, an important metric to track if you're concerned about solving Zone.js issues for proper functionality of NGSW and Server Side Rendering + +## Measuring First Input Delay + +First Input Delay (FID) measures the time from when a user first interacts with your site (i.e. when they click a link, tap on a button, or use a custom, JavaScript-powered control) to the time when the browser is actually able to respond to that interaction. [See the article on the Google Developer's Blog for more information on FID.](https://developers.google.com/web/updates/2018/05/first-input-delay) + +In order to track first input delay, you'll want to [polyfill the browser performance API](https://github.com/GoogleChromeLabs/first-input-delay): + +```bash +npm install --save-dev first-input-delay +``` + +Then add `import 'first-input-delay';` to your `src/polyfills.ts`. + +## Manual traces + +You can inject `AngularFirePerformance` to perform manual traces. + +```ts +constructor(private performance: AngularFirePerformance) {} + +// ... + +const trace = await this.performance.trace('some-trace'); +trace.start(); +// Dome something you want to trace +trace.stop(); +``` + +## RxJS operators + +AngularFire provides a number of RxJS operators which wrap the User Timing API. These are picked up by performance monitoring tools such as Chrome Inspector and Firebase Performance Monitoring. + +```ts +import { trace } from '@angular/fire/performance'; + +// ... + +constructor(private performance: AngularFirePerformance, private afs: AngularFirestore) {} + +ngOnInit() { + this.articles = afs.collection('articles') + .collection('articles', ref => ref.orderBy('publishedAt', 'desc')) + .snapshotChanges() + .pipe( + // measure the amount of time between the Observable being subscribed to and first emission (or completion) + trace('getArticles'), + map(articles => ...) + ); +} +``` + +### `trace(name: string)` + +The most basic operator, `trace` will measure the amount of time it takes for your observable to either complete or emit its first value. Beyond the basic trace there are several other operators: + +```ts +traceUntil( + name: string, + test: (T) => Boolean, + options?: { orComplete?: true } +) +``` + +Trace the observable until the first emission that passes the provided test. + +If the `orComplete` option is passed it will complete the trace when the observable completes, even if an emission never passed the provided test. + +```ts +traceWhile( + name: string, + test: (T) => Boolean, + options?: { orComplete?: true } +) +``` + +Starting with an emission that passes the provided test, trace until an emission fails the test. + +If the `orComplete` option is passed it will complete any existing trace when the observable completes. + +### `traceUntilLast(name: string)` + +Trace the observable until completion. + +### `traceUntilFirst(name: string)` + +Traces the observable until the first emission. + +## Configuration via Dependency Injection + +Set `INSTRUMENTATION_ENABLED` or `DATA_COLLECTION_ENABLED` to false disable all automatic and custom traces respectively. diff --git a/site/src/performance/index.md b/site/src/performance/index.md new file mode 100644 index 000000000..7a6ef7fce --- /dev/null +++ b/site/src/performance/index.md @@ -0,0 +1,5 @@ +--- +eleventyNavigation: + key: Performance + order: 10 +--- diff --git a/site/src/performance/performance.11tydata.json b/site/src/performance/performance.11tydata.json new file mode 100644 index 000000000..b924da1f3 --- /dev/null +++ b/site/src/performance/performance.11tydata.json @@ -0,0 +1,4 @@ +{ + "layout": "guide.njk", + "tags": "guides" +} diff --git a/docs/remote-config/getting-started.md b/site/src/remote-config/getting-started.md similarity index 93% rename from docs/remote-config/getting-started.md rename to site/src/remote-config/getting-started.md index 5c7412d17..e95d71649 100644 --- a/docs/remote-config/getting-started.md +++ b/site/src/remote-config/getting-started.md @@ -1,8 +1,15 @@ -

Getting started with Remote Config β

+--- +title: Getting started +eleventyNavigation: + key: Getting started + parent: Remote Config +--- + +## Getting started with Remote Config (BETA) `AngularFireRemoteConfig` dynamically imports the `firebase/remote-config` library on demand, provides convenience observables, pipes, and a promisified version of the [Firebase Remote Config SDK (`firebase.remoteConfig.RemoteConfig`)](https://firebase.google.com/docs/reference/js/firebase.remoteconfig.RemoteConfig). -### API: +## API ```ts class AngularFireRemoteConfigModule { } @@ -53,34 +60,29 @@ SETTINGS = InjectionToken; DEFAULTS = InjectionToken; ``` -## Configuration with Dependency Injection - -### Configure Remote Config with `SETTINGS` - Using the `SETTINGS` DI Token (*default: {}*) will allow you to [configure Firebase Remote Config](https://firebase.google.com/docs/reference/js/firebase.remoteconfig.Settings.html). -### Configure default values with `DEFAULTS` +## Configure default values Providing `DEFAULTS ({[key: string]: string | number | boolean})` tells `AngularFireRemoteConfig` to emit the provided defaults first. This allows you to count on Remote Config when the user is offline or in environments that the Remote Config service does not handle (i.e. Server Side Rendering). -## Putting it all together - ```ts +import { AngularFireRemoteConfigModule, DEFAULTS, SETTINGS } from '@angular/fire/remote-config'; + @NgModule({ imports: [ AngularFireModule.initializeApp(environment.firebase), AngularFireRemoteConfigModule ], providers: [ - { provide: DEFAULT_CONFIG, useValue: { enableAwesome: true } }, + { provide: DEFAULTS, useValue: { enableAwesome: true } }, { - provide: REMOTE_CONFIG_SETTINGS, + provide: SETTINGS, useFactory: () => isDevMode() ? { minimumFetchIntervalMillis: 10_000 } : {} } ] }) export class AppModule { } - ... constructor(remoteConfig: AngularFireRemoteConfig) { diff --git a/site/src/remote-config/index.md b/site/src/remote-config/index.md new file mode 100644 index 000000000..1334bae3a --- /dev/null +++ b/site/src/remote-config/index.md @@ -0,0 +1,5 @@ +--- +eleventyNavigation: + key: Remote Config + order: 9 +--- diff --git a/site/src/remote-config/remote-config.11tydata.json b/site/src/remote-config/remote-config.11tydata.json new file mode 100644 index 000000000..b924da1f3 --- /dev/null +++ b/site/src/remote-config/remote-config.11tydata.json @@ -0,0 +1,4 @@ +{ + "layout": "guide.njk", + "tags": "guides" +} diff --git a/site/src/rtdb/index.md b/site/src/rtdb/index.md new file mode 100644 index 000000000..7cb869514 --- /dev/null +++ b/site/src/rtdb/index.md @@ -0,0 +1,5 @@ +--- +eleventyNavigation: + key: RTDB + order: 4 +--- diff --git a/site/src/rtdb/lists.md b/site/src/rtdb/lists.md new file mode 100644 index 000000000..220c7d7ae --- /dev/null +++ b/site/src/rtdb/lists.md @@ -0,0 +1,252 @@ +--- +title: Lists +eleventyNavigation: + key: Lists + parent: RTDB +--- + +## Retrieving data as lists + +AngularFire synchronizes data as lists using the `AngularFireList` service. The `AngularFireList` service is not created by itself, but through the `AngularFireDatabase` service. The guide below demonstrates how to retrieve, save, and remove data as lists. + +## Injecting the `AngularFireDatabase` service + +*Make sure you have bootstrapped your application for AngularFire. See the Installation guide for bootstrap setup.* + +AngularFireDatabase is a service which can be injected through the constructor of your Angular component or `@Injectable()` service. In the previous step, we modified the `/src/app/app.component.ts` to retrieve data as an object. In this step, let's start with a clean slate. + +Replace your `/src/app/app.component.ts` from previous step to look like below. + +```ts +import { Component } from '@angular/core'; +import { AngularFireDatabase } from '@angular/fire/database'; + +@Component({ + selector: 'app-root', + templateUrl: 'app.component.html', + styleUrls: ['app.component.css'] +}) +export class AppComponent { + constructor(db: AngularFireDatabase) { } +} +``` + +In this section, we're going to modify the `/src/app/app.component.ts` to retrieve data as list, but before that let's look at ways around how to bind to a list. + +## Create a list binding + +Data is retrieved through the `AngularFireDatabase` service. The service is also generic. Provide the singular type and not the array type. + +```ts +const listRef = db.list('items'); +const shirtsRef = db.list('shirts'); +``` + +### Retrieve data + +To get the list in realtime, create a list binding as a property of your component or service. Then in your template, you can use the `async` pipe to unwrap the binding. + +Update `/src/app/app.component.ts` to import `AngularFireList` from `@angular/fire` and iterate through the list once data is retrieved. Also note the change in attribute `templateUrl` to inline `template` below. + +```ts +import { Component } from '@angular/core'; +import { AngularFireDatabase } from '@angular/fire/database'; +import { Observable } from 'rxjs'; + +@Component({ + selector: 'app-root', + template: `{%raw%} +
    +
  • + {{ item | json }} +
  • +
+ {%endraw%}`, +}) +export class AppComponent { + items: Observable; + constructor(db: AngularFireDatabase) { + this.items = db.list('items').valueChanges(); + } +} +``` + +## `AngularFireAction` - Action based API + +AngularFire provides methods that stream data back as redux compatible actions. This gives you extra horsepower when using libraries like Animations, ngrx, and ReactiveForms. + +### `valueChanges()` + +*What is it?* - Returns an Observable of data as a synchronized array of JSON objects. All Snapshot metadata is stripped and just the method provides only the data. + +*Why would you use it?* - When you just need a list of data. No snapshot metadata is attached to the resulting array which makes it simple to render to a view. + +*When would you not use it?* - When you need a more complex data structure than an array or you need the `key` of each snapshot for data manipulation methods. This method assumes you either are saving the `key` for the snapshot data or using a "readonly" approach. + +### `snapshotChanges()` + +*What is it?* - Returns an Observable of data as a synchronized array of `AngularFireAction[]`. + +*Why would you use it?* - When you need a list of data but also want to keep around metadata. Metadata provides you the underyling `DatabaseReference` and snapshot key. Having the snapshot's `key` around makes it easier to use data manipulation methods. This method gives you more horsepower with other Angular integrations such as ngrx, forms, and animations due to the `type` property. The `type` property on each `AngularFireAction` is useful for ngrx reducers, form states, and animation states. + +*When would you not use it?* - When you need a more complex data structure than an array or if you need to process changes as they occur. This array is synchronized with the remote and local changes in the Firebase Database. + +### `stateChanges()` + +*What is it?* - Returns an Observable of the most recent change as an `AngularFireAction`. + +*Why would you use it?* - The above methods return a singular `AngularFireAction` from each child event that occurs. `stateChanges()` emits changes as they occur rather than syncing the query order. This works well for ngrx integrations as you can build your own data structure in your reducer methods. + +*When would you not use it?* - When you just need a list of data. This is a more advanced usage of `AngularFireDatabase`. + +### `auditTrail()` + +*What is it?* - Returns an Observable of `AngularFireAction[]` as they occur. Similar to `stateChanges()`, but instead it keeps around the trail of events as an array. + +*Why would you use it?* - This method is like `stateChanges()` except it is not ephemeral. It collects each change in an array as they occur. This is useful for ngrx integrations where you need to replay the entire state of an application. This also works as a great debugging tool for all applications. You can simple write `db.list('items').auditTrail().subscribe(console.log)` and check the events in the console as they occur. + +*When would you not use it?* - When you just need a list of data. This is a more advanced usage of AngularFireDatabase. + +### Limiting events + +There are four child events: `"child_added"`, `"child_changed"`, `"child_removed"`, and `"child_moved"`. Each streaming method listens to all four by default. However, your site may only be intrested in one of these events. You can specify which events you'd like to use through the first parameter of each method: + +```ts +this.itemsRef = db.list('items'); +this.itemsRef.snapshotChanges(['child_added']) + .subscribe(actions => { + actions.forEach(action => { + console.log(action.type); + console.log(action.key); + console.log(action.payload.val()); + }); + }); +``` + +## API Summary + +The table below highlights some of the common methods on the `AngularFireList`. + +| method | | +| ---------|--------------------| +| `push(value: T)` | Creates a new record on the list, using the Realtime Database's push-ids. | +| `update(keyRefOrSnap: string, value: T)` | Firebase | AFUnwrappedSnapshot, value: Object) | Updates an existing item in the array. Accepts a key, database reference, or an unwrapped snapshot. | +| `remove(key: string?)` | Deletes the item by key. If no parameter is provided, the entire list will be deleted. | + +## Returning promises + +Each data operation method in the table above returns a promise. However, +you should rarely need to use the completion promise to indicate success, +because the realtime database keeps the list in sync. + +The promise can be useful to chain multiple operations, catching possible errors +from security rules denials, or for debugging. + +```ts +const promise = db.list('items').remove(); +promise + .then(_ => console.log('success')) + .catch(err => console.log(err, 'You do not have access!')); +``` + +## Adding new items + +Use the `push()` method to add new items on the list. + +```ts +const itemsRef = db.list('items'); +itemsRef.push({ name: newName }); +``` + +### Replacing items in the list using `set` + +Use the `set()` method to update existing items. + +```ts +const itemsRef = db.list('items'); +// to get a key, check the Example app below +itemsRef.set('key-of-some-data', { size: newSize }); +``` + +Replaces the current value in the database with the new value specified as the parameter. This is called a destructive update, because it deletes everything currently in place and saves the new value. + +## Updating items in the list using `update` + +Use the `update()` method to update existing items. + +```ts +const itemsRef = db.list('items'); +// to get a key, check the Example app below +itemsRef.update('key-of-some-data', { size: newSize }); +``` + +Note that this updates the current value with in the database with the new value specified as the parameter. This is called a non-destructive update, because it only updates the values specified. + +### Removing items from the list +Use the `remove()` method to remove data at the list item's location. + +```ts +const itemsRef = db.list('items'); +// to get a key, check the Example app below +itemsRef.remove('key-of-some-data'); +``` + +## Deleting the entire list + +If you omit the `key` parameter from `.remove()` it deletes the entire list. + +```ts +const itemsRef = db.list('items'); +itemsRef.remove(); +``` + +The following is a complete example of deleting an entire list. + +```ts +import { Component } from '@angular/core'; +import { AngularFireDatabase, AngularFireList } from '@angular/fire/database'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +@Component({ + selector: 'app-root', + template: ` +
    +
  • + + + +
  • +
+ + + + `, +}) +export class AppComponent { + itemsRef: AngularFireList; + items: Observable; + constructor(db: AngularFireDatabase) { + this.itemsRef = db.list('messages'); + // Use snapshotChanges().map() to store the key + this.items = this.itemsRef.snapshotChanges().pipe( + map(changes => + changes.map(c => ({ key: c.payload.key, ...c.payload.val() })) + ) + ); + } + addItem(newName: string) { + this.itemsRef.push({ text: newName }); + } + updateItem(key: string, newText: string) { + this.itemsRef.update(key, { text: newText }); + } + deleteItem(key: string) { + this.itemsRef.remove(key); + } + deleteEverything() { + this.itemsRef.remove(); + } +} +``` + diff --git a/site/src/rtdb/objects.md b/site/src/rtdb/objects.md new file mode 100644 index 000000000..efd9592bd --- /dev/null +++ b/site/src/rtdb/objects.md @@ -0,0 +1,179 @@ +--- +title: Objects +eleventyNavigation: + key: Objects + parent: RTDB +--- + +## Retrieving data as objects + +The `AngularFireObject` is a service for manipulating and streaming object data. The `AngularFireObject` service is not created by itself, but through the `AngularFireDatabase` service. + +## Injecting the `AngularFireDatabase` service + +*Make sure you have bootstrapped your application for AngularFire. See the Installation guide for bootstrap setup.* + +`AngularFireDatabase` is a service which can be injected through the constructor of your Angular component or `@Injectable()` service. + +If you've followed the earlier step "Installation and Setup" your `/src/app/app.component.ts` should look like below. + +```ts +import { Component } from '@angular/core'; +import { AngularFireDatabase } from '@angular/fire/database'; +import { Observable } from 'rxjs'; + +@Component({ + selector: 'app-root', + templateUrl: 'app.component.html', + styleUrls: ['app.component.css'] +}) +export class AppComponent { + items: Observable; + constructor(db: AngularFireDatabase) { + this.items = db.list('items').valueChanges(); + } +} +``` + +In this section, we're going to modify the `/src/app/app.component.ts` to retrieve data as object. + +## Create an object binding + +```ts +const relative = db.object('item').valueChanges(); +``` + +## Retrieve data + +To get the object in realtime, create an object binding as a property of your component or service. + +Then in your template, you can use the `async` pipe to unwrap the binding. + +```ts +import { Component } from '@angular/core'; +import { AngularFireDatabase } from '@angular/fire/database'; +import { Observable } from 'rxjs'; + +@Component({ + selector: 'app-root', + template: `{%raw%} +

{{ (item | async)?.name }}

+ {%endraw%}`, +}) +export class AppComponent { + item: Observable; + constructor(db: AngularFireDatabase) { + this.item = db.object('item').valueChanges(); + } +} +``` + +## API Summary + +The table below highlights some of the common methods on the `AngularFireObject`. + +| method | | +| ---------|--------------------| +| `set(value: T)` | Replaces the current value in the database with the new value specified as the parameter. This is called a **destructive** update, because it deletes everything currently in place and saves the new value. | +| `update(value: T)` | Updates the current value with in the database with the new value specified as the parameter. This is called a **non-destructive** update, because it only updates the values specified. | +| `remove()` | Deletes all data present at that location. Same as calling `set(null)`. | + +## Returning promises + +Each data operation method in the table above returns a promise. However, +you should rarely need to use the completion promise to indicate success, +because the realtime database keeps the object in sync. + +The promise can be useful to chain multiple operations, catching possible errors from security rules denials, or for debugging. + +```ts +const promise = db.object('item').remove(); +promise + .then(_ => console.log('success')) + .catch(err => console.log(err, 'You dont have access!')); +``` + +## Saving data + +Use the `set()` method for **destructive updates**. + +```ts +const itemRef = db.object('item'); +itemRef.set({ name: 'new name!'}); +``` + +## Updating data + +Use the `update()` method for **non-destructive updates**. + +```ts +const itemRef = db.object('item'); +itemRef.update({ age: newAge }); +``` + +**Only objects are allowed for updates, not primitives**. This is because +using an update with a primitive is the exact same as doing a `.set()` with a primitive. + +## Deleting data + +Use the `remove()` method to remove data at the object's location. + +```ts +const itemRef = db.object('item'); +itemRef.remove(); +``` + +**Example app**: + +```ts +import { Component } from '@angular/core'; +import { AngularFireDatabase, AngularFireObject } from '@angular/fire/database'; +import { Observable } from 'rxjs'; + +@Component({ + selector: 'app-root', + template: `{%raw%} +

{{ item | async | json }}

+ + +
+ + + + {%endraw%}`, +}) +export class AppComponent { + itemRef: AngularFireObject; + item: Observable; + constructor(db: AngularFireDatabase) { + this.itemRef = db.object('item'); + this.item = this.itemRef.valueChanges(); + } + save(newName: string) { + this.itemRef.set({ name: newName }); + } + update(newSize: string) { + this.itemRef.update({ size: newSize }); + } + delete() { + this.itemRef.remove(); + } +} +``` + +## Retrieving the snapshot + +AngularFire `valueChanges()` unwraps the Firebase DataSnapshot by default, but you can get the data as the original snapshot by using the `snapshotChanges()` option. + +```ts +this.itemRef = db.object('item'); +this.itemRef.snapshotChanges().subscribe(action => { + console.log(action.type); + console.log(action.key) + console.log(action.payload.val()) +}); +``` + +## Querying? + +Because `AngularFireObject` synchronizes objects from the realtime database, sorting will have no effect for queries that are not also limited by a range. For example, when paginating you would provide a query with a sort and filter. Both the sort operation and the filter operation affect which subset of the data is returned by the query; however, because the resulting object is simply json, the sort order will not be preseved locally. Hence, for operations that require sorting, you are probably looking for a list. diff --git a/site/src/rtdb/querying.md b/site/src/rtdb/querying.md new file mode 100644 index 000000000..3ba61b55d --- /dev/null +++ b/site/src/rtdb/querying.md @@ -0,0 +1,161 @@ +--- +title: Querying +eleventyNavigation: + key: Querying + parent: RTDB +--- + +## Querying lists + +Lists of data in the Realtime Database can be filtered down using specific querying methods. When these querying methods are combined with RxJS operators you can achieve dynamic querying which re-triggers the query when a source observable changes. This is great for updating queries in response to changes of an input or some other changing value. + +## Creating a query with constant values + +Queries are created by building on the [`firebase.database.Reference`](https://firebase.google.com/docs/reference/js/firebase.database.Reference). + +```ts +db.list('/items', ref => ref.orderByChild('size').equalTo('large')) +``` + +### Query options + +| Method | Purpose | +| ---------|--------------------| +| `orderByChild` | Specify a child to order by. | +| `orderByKey` | Boolean to order by Firebase Database keys. | +| `orderByValue` | Specify a value to order by. | +| ~~`orderByPriority`~~1 | Boolean to order by Firebase Database priority.| +| `equalTo`2 | Limit list to items that contain certain value. | +| `limitToFirst` | Sets the maximum number of items to return from the beginning of the ordered list of results. | +| `limitToLast` | Sets the maximum number of items to return from the end of the ordered list of results. | +| `startAt`2 | Return items greater than or equal to the specified key or value, depending on the order-by method chosen. | +| `endAt`2 | Return items less than or equal to the specified key or value, depending on the order-by method chosen. | + +:::annotations-section + 1 [This is the old way of doing things and is no longer recommended for use](https://youtu.be/3WTQZV5-roY?t=3m). Anything you can achieve with `orderByPriority` you should be doing with `orderByChild`. + + 2 The Firebase SDK supports an optional `key` parameter for [`startAt`](https://firebase.google.com/docs/reference/js/firebase.database.Reference#startAt), [`endAt`](https://firebase.google.com/docs/reference/js/firebase.database.Reference#endAt), and [`equalTo`](https://firebase.google.com/docs/reference/js/firebase.database.Reference#equalTo) when ordering by child, value, or priority. You can specify the `key` parameter using an object literal that contains the `value` and the `key`. For example: `startAt: { value: 'some-value', key: 'some-key' }`. +::: + +To learn more about how sorting and ordering data works in Firebase, check out the Firebase documentation on [working with lists of data](https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data). + +## Invalid query combinations + +*Queries can only be ordered by one method.* This means you can only specify +`orderByChild`, `orderByKey`, `orderByPriority`, or `orderByValue`. + +```ts +// WARNING: Do not copy and paste. This will not work! +ref.orderByChild('size').equalTo('large').orderByKey(true) +``` + +You can only use `limitToFirst` or `limitToLast`, but not both in combination. + +```ts +// WARNING: Do not copy and paste. This will not work! +ref.limitToFirst(10).limitToLast(100) +``` + +## Dynamic querying + +To enable dynamic queries one should lean on RxJS Operators like `switchMap`. + +An RxJS Subject is imported below. A Subject is like an Observable, but can multicast to many Observers. Subjects are like EventEmitters: they maintain a registry of many listeners. See, [What is a Subject](http://reactivex.io/rxjs/manual/overview.html#subject) for more information. + +When we call [`switchMap` on the Subject](https://www.learnrxjs.io/operators/transformation/switchmap.html), we can map each value to a new Observable; in this case a database query. + +```ts +const size$ = new Subject(); +const queryObservable = size$.pipe( + switchMap(size => + db.list('/items', ref => ref.orderByChild('size').equalTo(size)).valueChanges() + ) +); + +// subscribe to changes +queryObservable.subscribe(queriedItems => { + console.log(queriedItems); +}); + +// trigger the query +size$.next('large'); + +// re-trigger the query +size$.next('small'); +``` + +The following is an example of dynamic querying. [See this example in action on StackBlitz](https://stackblitz.com/edit/angularfire-db-api-s8ip7m). + +```ts +import { Component } from '@angular/core'; +import { AngularFireDatabase, AngularFireAction } from '@angular/fire/database'; +import { Observable, Subscription, BehaviorSubject } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; + +@Component({ + selector: 'app-root', + template: `{%raw%} +

Firebase widgets!

+
+
    +
  • + {{ item.payload.val().text }} + {{ item.payload.key }} +
  • +
+
No results, try clearing filters
+
+ Loading… +
+

Filter by size

+ + + + + +
+ {%endraw%}`, +}) +export class AppComponent { + items$: Observable[]>; + size$: BehaviorSubject; + + constructor(db: AngularFireDatabase) { + this.size$ = new BehaviorSubject(null); + this.items$ = this.size$.pipe( + switchMap(size => + db.list('/items', ref => + size ? ref.orderByChild('size').equalTo(size) : ref + ).snapshotChanges() + ) + ); + } + filterBy(size: string|null) { + this.size$.next(size); + } +} +``` + +*To run the above example as is, you need to have sample data in you firebase database with the following structure:* + + ```json +{ + "items": { + "a" : { + "size" : "small", + "text" : "small thing" + }, + "b" : { + "size" : "medium", + "text" : "medium sample" + }, + "c" : { + "size" : "large", + "text" : "large widget" + } + } +} + ``` + diff --git a/site/src/rtdb/rtdb.11tydata.json b/site/src/rtdb/rtdb.11tydata.json new file mode 100644 index 000000000..b924da1f3 --- /dev/null +++ b/site/src/rtdb/rtdb.11tydata.json @@ -0,0 +1,4 @@ +{ + "layout": "guide.njk", + "tags": "guides" +} diff --git a/site/src/shortcodes/buttons/index.js b/site/src/shortcodes/buttons/index.js new file mode 100644 index 000000000..a4761b1d3 --- /dev/null +++ b/site/src/shortcodes/buttons/index.js @@ -0,0 +1,33 @@ +/** + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const linkButton = { + name: "linkbutton", + type: "addPairedShortcode", + create (content, href, type='primary', external=false) { + const primaryClass = `link-button inline-block shadow-lg bg-blue text-white text-lg uppercase font-bold font-display tracking-wide rounded-lg px-8 py-3 text-center`; + const secondaryClass = `link-button inline-block shadow-lg bg-blue-200 text-black text-lg uppercase font-bold font-display tracking-wide rounded-lg px-8 py-3 text-center`; + const cssClass = type === 'primary' ? primaryClass : secondaryClass; + const externalAttrs = external ? 'rel="noopener" target="blank"' : ''; + return `${content}`; + } +} + +module.exports = { + shortcodes: [ + linkButton, + ] +}; diff --git a/site/src/shortcodes/disclaimerprod/index.js b/site/src/shortcodes/disclaimerprod/index.js new file mode 100644 index 000000000..1fe9f9c81 --- /dev/null +++ b/site/src/shortcodes/disclaimerprod/index.js @@ -0,0 +1,33 @@ +/** + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Usage: {% disclaimerprod %} +const disclaimerprod = { + name: "disclaimerprod", + type: "addShortcode", + create() { + return `
+

Beta Site!

+

This is a brand new guide site that is in beta. During this time period we'd love to hear your feedback on our GitHub Discussion board. Please let us know what you think of the usability, content, and any ideas for improvement. All contributors are welcome!

+
`; + } +} + +module.exports = { + shortcodes: [ + disclaimerprod + ] +}; diff --git a/site/src/shortcodes/filters/index.js b/site/src/shortcodes/filters/index.js new file mode 100644 index 000000000..98d8916b9 --- /dev/null +++ b/site/src/shortcodes/filters/index.js @@ -0,0 +1,104 @@ +const console = require('console'); +const { resolve } = require('path'); + +const findByName = { + name: "findByName", + type: "addNunjucksFilter", + create(list, name) { + return list.find((item) => item.name === name); + } +}; + +const log = { + name: "log", + type: "addNunjucksFilter", + create(object, logName) { + console.log(logName, object); + return object; + } +}; + +const json = { + name: "json", + type: "addNunjucksFilter", + create(object, spacer = 3) { + let cache = []; + const json = JSON.stringify( + object, + (key, value) => { + if (typeof value === "object" && value !== null) { + if (cache.includes(value)) { + return; + } + cache.push(value); + } + return value; + }, + spacer + ); + cache = null; + return json; + } +}; + +const findPreviousEntry = { + name: "findPreviousEntry", + type: "addNunjucksFilter", + create(children, eleventyNavigation) { + const itemIndex = children.findIndex(entry => entry.key === eleventyNavigation.key); + const previousIndex = itemIndex - 1; + return children[previousIndex]; + } +}; + +const findNextEntry = { + name: "findNextEntry", + type: "addNunjucksFilter", + create(children, eleventyNavigation) { + const itemIndex = children.findIndex(entry => entry.key === eleventyNavigation.key); + const nextIndex = itemIndex + 1; + return children[nextIndex]; + } +}; + +/** + * This filter reads the custom navigation in the global _data/ folder + * and merges it with the eleventyNavigation config. Eleventy Navigation + * works great for parent folders but it's less good for child navigation + * when it comes to "next/prev" routing. This allows us to keep the good + * parts of Eleventy Navigation and have a custom child path routing. + * + * Eventually I'd like to customize Eleventy Navigation to do child routing + * because this is extremely inefficient to loop over nav for every page. It + * doesn't effect this build too bad though. + */ +const mergeNavigation = { + name: "mergeNavigation", + type: "addNunjucksFilter", + create(eleventyNavigation) { + const customNavigation = require(resolve(__dirname, '../../_data/nextprev.json')); + const customKeys = Object.keys(customNavigation); + customKeys.forEach(key => { + const eleventyNavMatch = eleventyNavigation.find(item => item.key === key); + if(eleventyNavMatch != undefined) { + const matchKids = eleventyNavMatch.children; + const newKids = customNavigation[key].children.map(child => { + return matchKids.find(c => c.key === child.key); + }); + eleventyNavigation.find(item => item.key === key).children = newKids; + } + }); + return eleventyNavigation; + } +} + +module.exports = { + shortcodes: [ + findByName, + log, + json, + findPreviousEntry, + findNextEntry, + mergeNavigation, + ], +}; diff --git a/site/src/shortcodes/headings/index.js b/site/src/shortcodes/headings/index.js new file mode 100644 index 000000000..57f1a72e0 --- /dev/null +++ b/site/src/shortcodes/headings/index.js @@ -0,0 +1,40 @@ +/** + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Usage: {% headingone %} My title! {% endheadingone %} +const headingOne = { + name: "headingone", + type: "addPairedShortcode", + create (content) { + return `

${ content }

` + } +}; + +const subHeading = { + name: "subheading", + type: "addPairedShortcode", + create (content) { + return `
${content}
`; + } +}; + +module.exports = { + shortcodes: [ + headingOne, + subHeading, + ] +}; + diff --git a/site/src/shortcodes/includecode/fetch.js b/site/src/shortcodes/includecode/fetch.js new file mode 100644 index 000000000..d122d5be2 --- /dev/null +++ b/site/src/shortcodes/includecode/fetch.js @@ -0,0 +1,33 @@ +/** + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const fetch = require("node-fetch"); + +function convertToGitHubApiUrl(githubPath) { + const urlPieces = githubPath.split('/'); + const [user, repo] = urlPieces.slice(0, 2); + // TODO(davideast): Don't hardcode main branch + const githubApiUrl = [user, repo, 'master', ...urlPieces.slice(2, urlPieces.length)].join('/'); + return `https://raw.githubusercontent.com/${githubApiUrl}`; +} + +async function fetchCode(githubPath) { + const githubApiUrl = convertToGitHubApiUrl(githubPath); + const response = await fetch(githubApiUrl); + return response.text(); +} + +module.exports = { fetchCode }; diff --git a/site/src/shortcodes/includecode/from-local.js b/site/src/shortcodes/includecode/from-local.js new file mode 100644 index 000000000..9db02271b --- /dev/null +++ b/site/src/shortcodes/includecode/from-local.js @@ -0,0 +1,24 @@ +const { readFile } = require('fs'); +const { resolve } = require('path'); +const { promisify } = require('util'); +const readFileAsync = promisify(readFile); + +function convertGitHubPathToLocal(githubPath) { + return resolve(__dirname, '../../../repo_clones', githubPath); +} + +async function fetchCode(githubPath = '') { + let content = ''; + try { + const localPath = convertGitHubPathToLocal(githubPath); + content = await readFileAsync(localPath, 'utf-8'); + } catch(error) { + console.error(error); + content = 'File not found 😭'; + } + return content; +} + +module.exports = { + fetchCode, +}; diff --git a/site/src/shortcodes/includecode/index.js b/site/src/shortcodes/includecode/index.js new file mode 100644 index 000000000..3d0a6526d --- /dev/null +++ b/site/src/shortcodes/includecode/index.js @@ -0,0 +1,105 @@ +/** + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const { fetchCode } = require('./from-local'); +const { processSnippet } = require('./snippets'); +const prism = require('markdown-it-prism'); +const MarkdownIt = require('markdown-it'); +const md = new MarkdownIt({ html: true }); +md.use(prism); + +function embedInCodeticks(code) { + return '```js\n' + code + '\n```'; +} + +// Usage: {% includecode github_path="firebase/snippets-web/snippets/auth-next/anonymous/auth_anon_sign_in.js" %} +const includecode = { + name: "includecode", + type: "addNunjucksAsyncShortcode", + create({ github_path }) { + return fetchCode(github_path) + .then(processSnippet) + .then(embedInCodeticks) + .then(output => md.render(output)); + } +}; + +// Usage: {% codeswitcher eap_github_path="" current_github_path="" %} +const codeswitcher = { + name: "codeswitcher", + type: "addNunjucksAsyncShortcode", + async create({ eap_github_path, current_github_path }) { + + let eapCode = ''; + if(eap_github_path != undefined && eap_github_path !== '') { + eapCode = await fetchCode(eap_github_path); + eapCode = processSnippet(eapCode); + eapCode = embedInCodeticks(eapCode); + eapCode = md.render(eapCode); + eapCode = eapCode.trim(); + } + + let currentCode = ''; + if(current_github_path != undefined && current_github_path !== '') { + currentCode = await fetchCode(current_github_path); + currentCode = processSnippet(currentCode); + currentCode = embedInCodeticks(currentCode); + currentCode = md.render(currentCode); + eapCode = eapCode.trim(); + } + const eapId = Math.random().toString(36).substring(7); + const currentId = Math.random().toString(36).substring(7); + return /*html*/` + + + + + + ${eapCode} + + +`; + } +}; + +// Usage: {% commonexample title="" eap_github_path="" current_github_path="" %} +const commonexample = { + name: "commonexample", + type: "addNunjucksAsyncShortcode", + async create({ title, eap_github_path, current_github_path, github_path }) { + const isEmpty = value => value == undefined || value === ''; + const isSwitcher = (!isEmpty(eap_github_path) || !isEmpty(github_path)) && !isEmpty(current_github_path); + // TODO(davideast): Enable current_github_path as a single option + const pathToUse = !isEmpty(github_path) ? github_path : eap_github_path; + const codebox = isSwitcher ? + await codeswitcher.create({ eap_github_path: pathToUse, current_github_path }) : + await includecode.create({ github_path: pathToUse }); + return md.render(`### ${title} +${codebox}`); + } +}; + +module.exports = { + shortcodes: [ + includecode, + codeswitcher, + commonexample, + ] +}; diff --git a/site/src/shortcodes/includecode/snippets.js b/site/src/shortcodes/includecode/snippets.js new file mode 100644 index 000000000..476ddf334 --- /dev/null +++ b/site/src/shortcodes/includecode/snippets.js @@ -0,0 +1,73 @@ +// Modified from: https://github.com/firebase/snippets-web/blob/master/scripts/separate-snippets.ts + +// Regex for [START] and [END] snippet tags. +const RE_START_SNIPPET = /\[START\s+([A-Za-z_]+)\s*\]/; +const RE_END_SNIPPET = /\[END\s+([A-Za-z_]+)\s*\]/; + +function isBlank(line) { + return line.trim().length === 0; +} + +/** + * Change all [START foo] and [END foo] to be [START foosuffix] and [END foosuffix] + */ +function removeSectionsFromSnippet(lines/* string[]*/) { + const outputLines = []; + for (const line of lines) { + if (!line.match(RE_START_SNIPPET) && !line.match(RE_END_SNIPPET)) { + outputLines.push(line); + } + } + return outputLines; +} + +/** + * Remove all left-padding so that the least indented line is left-aligned. + */ +function adjustIndentation(lines /*: string[]*/) { + const nonBlankLines = lines.filter((l) => !isBlank(l)); + const indentSizes = nonBlankLines.map((l) => l.length - l.trimLeft().length); + const minIndent = Math.min(...indentSizes); + + const outputLines = []; + for (const line of lines) { + if (isBlank(line)) { + outputLines.push(""); + } else { + outputLines.push(line.substr(minIndent)); + } + } + return outputLines; +} + +/** + * If the first line after leading comments is blank, remove it. + */ +function removeFirstLineAfterComments(lines /*: string[]*/) { + const outputLines = [...lines]; + const firstNonComment = outputLines.findIndex( + (l) => l.startsWith("// [START") + ); + return outputLines.slice(firstNonComment, outputLines.length); +} + +/** + * Turns a series of source lines into a standalone snippet file by running + * a series of transformations. + * + * @param cones the code containing the snippet (including START/END comments) + */ +function processSnippet(code /*: string[]*/) /*: string*/ { + const lines = code.split('\n'); + let outputLines = [...lines]; + + // Perform transformations individually, in order + outputLines = removeFirstLineAfterComments(outputLines); + outputLines = removeSectionsFromSnippet(outputLines); + outputLines = adjustIndentation(outputLines); + + const content = [...outputLines].join("\n"); + return content; +} + +module.exports = { processSnippet }; diff --git a/site/src/shortcodes/includecode/transform.js b/site/src/shortcodes/includecode/transform.js new file mode 100644 index 000000000..f90ac78c2 --- /dev/null +++ b/site/src/shortcodes/includecode/transform.js @@ -0,0 +1,65 @@ +/** + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const { parse } = require("@babel/parser") +const generate = require('@babel/generator').default; +const prettier = require("prettier"); + +function transform(githubCode) { + const parsedAST = parse(githubCode, { + sourceType: "module", + }); + + const isFirst = index => index === 0; + + parsedAST.program.body.forEach((statement, index) => { + + // The first set of lines usually have comments and we + // always want them removed + if(isFirst(index)) { + // Remove all comments before leading statement + if(statement.leadingComments) { + delete statement.leadingComments; + } + } + + // Find any [START] or [END] + if(statement.leadingComments) { + statement.leadingComments = statement.leadingComments.filter(comment => { + return !comment.value.includes('[START') && !comment.value.includes('[END'); + }); + } + + // Remove any trailing comments because they likely should be + // leading comments. Babel guesses where comments go and you can + // find a comment as both trailing and leading. This will likely + // cause problems in the future, but right now it works with the + // code samples we use. + if(statement.trailingComments) { + statement.trailingComments = []; + } + }); + + const { code } = generate({ + type: "Program", + // passing a new copy of the body to avoid + // any reference problems + body: parsedAST.program.body.slice(), + }); + return prettier.format(code, { parser: "babel" }); +} + +module.exports = { transform }; diff --git a/site/src/shortcodes/index.js b/site/src/shortcodes/index.js new file mode 100644 index 000000000..2d019e878 --- /dev/null +++ b/site/src/shortcodes/index.js @@ -0,0 +1,41 @@ +/** + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const { readdirSync, lstatSync } = require('fs'); +const { resolve } = require('path'); + +/** + * This sets up the shortcodes plugin to dynamically register + * any shortcode in this directory, as long as it is is in + * its own directory, exported in an index.js with an exports + * of an array of shortcodes. + * + * Example: + * / shortcodes + * / includecode + * + index.js + * module.exports = { shortcodes: [...] } + */ + +const shortcodes = readdirSync(__dirname) + .map(relativePath => resolve(__dirname, relativePath)) + .filter(absolutePath => lstatSync(absolutePath).isDirectory()) + .map(path => require(path).shortcodes) + .flat(); + +module.exports = { + shortcodes +}; diff --git a/site/src/shortcodes/version/index.js b/site/src/shortcodes/version/index.js new file mode 100644 index 000000000..977491152 --- /dev/null +++ b/site/src/shortcodes/version/index.js @@ -0,0 +1,30 @@ +/** + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Usage: {% version %} +const version = { + name: "version", + type: "addShortcode", + create () { + return Date.now().toString(); + } +}; + +module.exports = { + shortcodes: [ + version, + ] +}; diff --git a/docs/storage/storage.md b/site/src/storage/getting-started.md similarity index 88% rename from docs/storage/storage.md rename to site/src/storage/getting-started.md index 43b6c5a0b..ec28292ac 100644 --- a/docs/storage/storage.md +++ b/site/src/storage/getting-started.md @@ -1,8 +1,16 @@ -# AngularFireStorage +--- +title: Getting started +eleventyNavigation: + key: Getting started + parent: Storage +--- -> Cloud Storage is designed to help you quickly and easily store and serve user-generated content, such as photos and videos. -### Import the `NgModule` +## Using AngularFireStorage + +Cloud Storage is designed to help you quickly and easily store and serve user-generated content, such as photos and videos. + +## Import the `NgModule` Cloud Storage for AngularFire is contained in the `@angular/fire/storage` module namespace. Import the `AngularFireStorageModule` in your `NgModule`. This sets up the `AngularFireStorage` service for dependency injection. @@ -40,7 +48,7 @@ import {AngularFireStorageModule, BUCKET } from '@angular/fire/storage'; export class AppModule {} ``` -### Injecting the AngularFireStorage service +## Injecting the AngularFireStorage service Once the `AngularFireStorageModule` is registered you can inject the `AngularFireStorage` service. @@ -57,7 +65,7 @@ export class AppComponent { } ``` -### Uploading blobs +## Uploading blobs There are three options for uploading files. @@ -68,9 +76,7 @@ There are three options for uploading files. | `putString(data: string, format?: StringFormat, metadata?: UploadMetadata): AngularFireUploadTask` | Updates an existing item in the array. Accepts a key, database reference, or an unwrapped snapshot. | | `upload(path: string, data: StringFormat, metadata?: UploadMetadata): AngularFireUploadTask` | Upload or update a new file to the storage reference's path. Returns an `AngularFireUploadTask` for upload monitoring. | -### Examples - -#### Uploading blobs with put +## Uploading blobs with put ```ts import { Component } from '@angular/core'; @@ -93,7 +99,7 @@ export class AppComponent { } ``` -#### Uploading blobs with putString +## Uploading blobs with putString ```ts import { Component } from '@angular/core'; @@ -116,7 +122,7 @@ export class AppComponent { } ``` -#### Uploading files with upload +## Uploading files with upload ```ts import { Component } from '@angular/core'; @@ -138,7 +144,7 @@ export class AppComponent { } ``` -### Monitoring upload percentage +## Monitoring upload percentage An `AngularFireUploadTask` has methods for observing upload percentage as well as the final download URL. @@ -148,8 +154,6 @@ An `AngularFireUploadTask` has methods for observing upload percentage as well a | `percentageChanges(): Observable` | Emits the upload completion percentage. | | `getDownloadURL(): Observable` | Emits the download url when available | -#### Example Usage - The method `getDownloadURL()` doesn't rely on the task anymore, hence, in order to get the url we should use the finalize method from RxJS on top of the storage ref. ```ts @@ -157,11 +161,11 @@ import { finalize } from 'rxjs/operators'; @Component({ selector: 'app-root', - template: ` + template: `{%raw%}
{{ uploadPercent | async }}
{{ downloadURL | async }} - ` + {%endraw%}` }) export class AppComponent { uploadPercent: Observable; @@ -184,7 +188,17 @@ export class AppComponent { } ``` -### Downloading Files +## Downloading Files + +A convenient pipe exists for simple in page references. + +```ts +@Component({ + selector: 'app-root', + template: `` +}) +export class AppComponent {} +``` To download a file you'll need to create a reference and call the `getDownloadURL()` method on an `AngularFireStorageReference`. @@ -202,18 +216,16 @@ export class AppComponent { } ``` -### Managing Metadata +## Managing Metadata Cloud Storage for Firebase allows you to upload and download metadata associated with files. This is useful because you can store important metadata and download it without needing to download the entire file. -### Examples - -#### Downloading metadata +### Downloading metadata ```ts @Component({ selector: 'app-root', - template: `
{{ meta | async }}
` + template: `{%raw%}
{{ meta | async }}
{%endraw%}` }) export class AppComponent { meta: Observable; @@ -224,7 +236,7 @@ export class AppComponent { } ``` -#### Uploading metadata with files +### Uploading metadata with files ```ts @Component({ diff --git a/site/src/storage/index.md b/site/src/storage/index.md new file mode 100644 index 000000000..08ae2d15e --- /dev/null +++ b/site/src/storage/index.md @@ -0,0 +1,6 @@ +--- +eleventyNavigation: + key: Storage + order: 6 +--- + diff --git a/site/src/storage/storage.11tydata.json b/site/src/storage/storage.11tydata.json new file mode 100644 index 000000000..b924da1f3 --- /dev/null +++ b/site/src/storage/storage.11tydata.json @@ -0,0 +1,4 @@ +{ + "layout": "guide.njk", + "tags": "guides" +} diff --git a/site/src/styles/prism.css b/site/src/styles/prism.css new file mode 100644 index 000000000..23874e8ce --- /dev/null +++ b/site/src/styles/prism.css @@ -0,0 +1,187 @@ +/** + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * prism.js custom theme for JavaScript, CSS and HTML + * Based on default theme + */ + + code[class*="language-"] { + padding: 0; +} + +code[class*="language-"], +pre[class*="language-"] { + color: aliceblue; + background: none; + font-size: 14px; + font-family: 'Roboto Mono', monospace; + font-weight: 400; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +code[class*="language-css"], +pre[class*="language-css"], +code[class*="language-html"], +pre[class*="language-html"] { + color: #d0d2d1 !important; +} + +code[class*="language-html"].token.punctuation, +pre[class*="language-html"].token.punctuation { + color: #4dd0e1 !important; +} + +pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { + text-shadow: none; + background: hsl(213, 92%, 85%); + opacity: 0.4; +} + +pre[class*="language-"]::selection, pre[class*="language-"] ::selection, +code[class*="language-"]::selection, code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; + opacity: 0.4; +} + +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + overflow: auto; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + border-radius: 8px; + background-color: #283142; + margin-bottom: 2rem; + margin-top: 2rem; + /*TODO:(davideast) We have to set the max-width of the codeblock because + it causes the content to break out of the viewport. The "break out" + causes those annoying x axis overflows and scrolling on mobile devices. + Ideally there's a better solution. + */ + max-width:100%; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .1em; + border-radius: .3em; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: #f06292; +} + +.token.punctuation { + color: aliceblue; +} + +.namespace { + opacity: .7; +} + +.token.function, +.token.property { + color: #4dd0e1; +} + +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #9ccc65; +} + +.token.tag, +.token.selector, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #9ccc65; +} + +.token.attr-name, +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #9ccc65; +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #4dd0e1; +} + +.token.class-name { + color: #4dd0e1; +} + +.token.regex, +.token.important, +.token.variable { + color: #4dd0e1; +} + +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} + +eap-tab-panel :not(pre) > code[class*="language-"], +eap-tab-panel pre[class*="language-"] { + padding: 0; + margin: 0; +} diff --git a/site/src/styles/tailwind.config.js b/site/src/styles/tailwind.config.js new file mode 100644 index 000000000..5103d1bb0 --- /dev/null +++ b/site/src/styles/tailwind.config.js @@ -0,0 +1,50 @@ +/** + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +module.exports = { + purge: [ + "src/**/*.njk", + "src/**/*.md", + "src/**/*.js", + ], + darkMode: false, // or 'media' or 'class' + theme: { + fontFamily: { + body: ['Roboto', 'Arial', 'sans-serif'], + display: ['Google Sans', 'Arial', 'sans-serif'], + mono: ['Roboto Mono', 'monospace'], + }, + extend: { + colors: { + 'black': 'hsl(0 0% 0% / 87%)', + 'blue-200': 'hsl(214 82% 50% / 7%)', + 'blue': 'hsl(214 82% 50%)', + 'navy': '#283142', + 'grey': '#DADCE0', + 'grey-200': '#ECEFF1', + 'grey-300': 'hsl(0 0% 0% / 54%)', + 'grey-600': 'hsl(213 5% 39% / 1)', + 'grey-700': 'hsl(213 5% 19% / 1)', + 'yellow': 'hsl(37 100% 94%)', + 'orange': "#FF8F00", + 'green': '#6CFF38', + } + }, + }, + variants: { + extend: {}, + }, + plugins: [], +} \ No newline at end of file diff --git a/site/src/styles/tailwind.css b/site/src/styles/tailwind.css new file mode 100644 index 000000000..39284b1a0 --- /dev/null +++ b/site/src/styles/tailwind.css @@ -0,0 +1,331 @@ +/** + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + @import "tailwindcss/base"; + @import "tailwindcss/components"; + @import "tailwindcss/utilities"; + + @font-face { + font-family: 'Google Sans'; + font-style: normal; + font-weight: 400; + src: url('/assets/GoogleSans-Regular.woff2') format('woff2'); + } + + @font-face { + font-family: 'Google Sans'; + font-style: normal; + font-weight: 500; + src: url('/assets/GoogleSans-Medium.woff2') format('woff2'); + } + + @font-face { + font-family: 'Google Sans'; + font-style: bold; + font-weight: 900; + src: url('/assets/GoogleSans-Bold.woff2') format('woff2'); + } + + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: url('/assets/Roboto-Regular.woff2') format('woff2'); + } + + @font-face { + font-family: 'Roboto'; + font-style: italic; + font-weight: 400; + src: url('/assets/Roboto-Italic.woff2') format('woff2'); + } + + @font-face { + font-family: 'Roboto'; + font-style: bold; + font-weight: 900; + src: url('/assets/Roboto-900.woff2') format('woff2'); + } + + @font-face { + font-family: 'Roboto Mono'; + font-style: normal; + font-weight: 400; + src: url('/assets/RobotoMono-Regular.woff2') format('woff2'); + } + + body { + background-color: hsl(255 1% 98%); + background-image: url(/assets/corner.svg); + background-position: top right; + background-repeat: no-repeat; + width: 100vw; + } + + a[aria-current="page"] { + @apply text-blue; + } + + .docs-content main { + @apply grid min-h-screen; + grid-template-columns:[side] auto 1fr [stack]; + grid-template-rows: [side] 1fr; + column-gap: 8vw; + } + + .docs-content main > aside { + min-width: 32ch; + @apply sticky top-0 z-10 h-screen pl-8 pr-4 overflow-y-scroll; + } + + .docs-content main > article { + max-width: 64ch; + @apply min-h-screen pr-8; + } + + h1, h2, h3, h4, h5, h6 { + @apply font-display; + } + + h1 { + @apply text-5xl font-bold font-display; + } + + .docs-content main > article h2 { + @apply mt-12 mb-4 antialiased font-bold leading-snug text-black; + font-size: 24px; + } + + .docs-content main > article h3 { + @apply mt-8 text-xl; + } + + .docs-content main > article h4 { + @apply mt-2 text-lg leading-snug text-grey-600; + } + + .docs-content main > article p { + @apply mb-6 text-lg; + line-height: 2rem; + } + + /** + Don't go further than direct descendants for code styling + otherwise it will mess with the codeboxes + */ + .docs-content main > article p > code { + @apply p-1 bg-gray-200 rounded-md; + } + + .docs-content article p > b { + @apply font-display; + } + + .docs-content article ol, .docs-content article ul:not(.prevnext-grid) { + list-style-type: initial; + padding: 0 2rem; + margin: 1rem 0; +} + + table a, article p > a:not(.link-button) { + @apply underline text-blue; + } + + .annotations-section p { + @apply text-sm !important; + line-height: 1.35rem !important; + } + + @media (max-width: 775px) { + + .docs-content main { + column-gap: unset; + } + + .docs-content main > article { + @apply w-screen p-2; + } + + .docs-content main > aside, + .docs-content main > article { + grid-area: side; + } + + .docs-content main > aside { + transition: transform 0.25s cubic-bezier(0.445, 0.05, 0.55, 0.95); + transform: translateX(-200%); + } + + .docs-content main > aside.slideIn { + transform: translateX(0); + } + + .landing-container, .landing-faq { + width: 100vw !important; + padding-left: 2rem; + padding-right: 2rem; + } + + } + + @media (max-width: 420px) { + .landing-container h1 { + @apply text-4xl; + } + body.landing-content { + background-image: none; + } + } + + /* Thanks to https://css-tricks.com/responsive-data-tables/ */ + table { + width: 100%; + border-collapse: collapse; + overflow-x: scroll; + overflow-wrap: break-word; + @apply text-sm; + } + /* Zebra striping */ + tr:nth-of-type(odd) { + @apply bg-gray-100; + } + th { + @apply font-bold tracking-wide uppercase font-display; + } + td, th { + @apply p-2 text-left; + } + + @media + only screen and (max-width: 760px), + (min-device-width: 768px) and (max-device-width: 1024px) { + + /* Stack the table */ + table, thead, tbody, th, td, tr { + display: block; + } + + } + + eap-tab-switcher { + @apply block w-full overflow-x-hidden bg-navy rounded-t-md; + } + + .docs-content article eap-tab-switcher { + @apply mb-6; + } + + eap-tab-list { + @apply flex items-center h-16 bg-white border border-solid border-grey rounded-t-md font-display; + } + + eap-tab-list button { + @apply p-4 text-sm font-medium text-gray-600; + } + + eap-tab-list button[aria-selected="true"] { + @apply text-blue; + } + + eap-tab-panel-list { + @apply block p-4 overflow-x-scroll; + } + + eap-tab-panel { + @apply block; + } + + eap-tab-panel pre code { + @apply bg-navy; + } + + eap-tab-panel { + @apply text-white; + } + + .docs-content h3 + pre[class*="language-"], h3 + eap-tab-switcher { + margin-top: .5rem; + } + + eap-click-card { + @apply block; + } + + .landing-container { + width: 84ch; + margin: 0 auto; + } + + .landing-faq { + width: 64ch; + } + + .code-editor { + display: grid; + } + + .code-editor pre[class="language-js"] { + border-radius: 0; + margin: 0; + padding: 2rem; + } + + .code-editor .terminal { + height: 100%; + padding: 1rem; + } + + .code-editor .terminal div { + opacity: 0; + overflow: hidden; + margin: 1rem; + } + + .code-editor .terminal div:nth-child(2) { + animation: fadeIn 400ms cubic-bezier(0.47, 0, 0.745, 0.715) 2.5s forwards; + } + + .code-editor .terminal div:nth-child(3) { + animation: fadeIn 400ms cubic-bezier(0.47, 0, 0.745, 0.715) 4s forwards; + } + + .code-editor .terminal div:nth-child(4) { + animation: fadeIn 400ms cubic-bezier(0.47, 0, 0.745, 0.715) 5.5s forwards; + } + + .code-editor .terminal div:nth-child(5), #btnShowCode { + animation: fadeIn 400ms cubic-bezier(0.47, 0, 0.745, 0.715) 7.5s forwards; + } + + /* Thanks to https://css-tricks.com/snippets/css/typewriter-effect/ */ + .terminal .typewriter { + overflow: hidden; /* Ensures the content is not revealed until the animation */ + white-space: nowrap; /* Keeps the content on a single line */ + margin: 0 auto; /* Gives that scrolling effect as the typing happens */ + letter-spacing: .1em; /* Adjust as needed */ + animation: typing 3.65s steps(40, end) 1s forwards; + } + + /* The typing effect */ + @keyframes typing { + from { opacity:1; width: 0 } + to { width: 100%; opacity:1; } + } + + @keyframes fadeIn { + from { opacity: 0 } + to { opacity: 1; } + } \ No newline at end of file diff --git a/site/src/universal/cloud-functions.md b/site/src/universal/cloud-functions.md new file mode 100644 index 000000000..780edd4a2 --- /dev/null +++ b/site/src/universal/cloud-functions.md @@ -0,0 +1,79 @@ +--- +title: Cloud Functions +eleventyNavigation: + key: Cloud Functions + parent: Universal +--- + +## Deploying on Universal sites on Cloud Functions + +After [setting up your application with Angular Universal as outlined in Getting Started](getting-started.md), you're now ready to build your application for Firebase Hosting & Cloud Functions. + +Cloud Functions for Firebase lets you automatically run backend code in response to events triggered by Firebase features and HTTPS requests. Your code is stored in Google's cloud and runs in a managed environment. There's no need to manage and scale your own servers. [Learn more about Cloud Functions for Firebase](https://firebase.google.com/docs/functions/). + +If you don't already have the Firebase CLI installed, do so: + +```bash +npm i -g firebase-tools +firebase login +``` + +Then inside your project root, setup your Firebase CLI project: + +```bash +firebase init +``` + +Configure whichever features you'd want to manage but make sure to select at least `functions` and `hosting`. Choose Typescript for Cloud Functions and use the default `public` directory for Hosting. + +After you're configured, you should now see a `firebase.json` file in your project root. Let's add the following `rewrites` directive to it: + +```js +{ + // ... + "hosting": { + // ... + "rewrites": [ + { "source": "**", "function": "universal" } + ] + } +} +``` + +This will inform Firebase Hosting that it should proxy all requests to Cloud Functions, if a file isn't already present in the hosting directory. Let's go ahead and modify your `package.json` to build for Cloud Functions: + +```js +"scripts": { + // ... omitted + "build": "ng build && npm run copy:hosting && npm run build:ssr && npm run build:functions", + "copy:hosting": "cp -r ./dist/YOUR_PROJECT_NAME/* ./public && rm ./public/index.html", + "build:functions": "npm run --prefix functions build" +}, +``` + +Change the build script in your `functions/package.json` to the following: + +```js +"scripts": { + // ... omitted + "build": "rm -r ./dist && cp -r ../dist . && tsc", +} +``` + +Finally, add the following to your `functions/src/index.ts`: + +```ts +export const universal = functions.https.onRequest((request, response) => { + require(`${process.cwd()}/dist/YOUR_PROJECT_NAME-webpack/server`).app(request, response); +}); +``` + +We you should now be able to run `npm run build` to build your project for Firebase Hosting and Cloud Functions. + +To test, spin up the emulator with `firebase serve`. Once you've confirmed it's working go ahead and `firebase deploy`. + +## Additional Resources + +- [Universal Starter Template](https://github.com/angular/universal-starter) +- [FireShip: Angular Universal SSR with Firebase](https://fireship.io/lessons/angular-universal-firebase/) + diff --git a/site/src/universal/getting-started.md b/site/src/universal/getting-started.md new file mode 100644 index 000000000..52bfa6536 --- /dev/null +++ b/site/src/universal/getting-started.md @@ -0,0 +1,158 @@ +--- +title: Getting started +eleventyNavigation: + key: Getting started + parent: Universal +--- + +## Getting started with AngularFire and Universal + +Server-side rendering (SSR) is the process of converting a JavaScript app to plain HTML at request-time, allowing search engine crawlers and linkbots to understand page content reliably. + +## Prerequisites + +- `@angular/cli >= v6.0` +- `@angular/fire >= v5.0.0` + +## Generate the Angular Universal Server Module + +First, create a server module with the Angular CLI. + +```bash +ng generate universal --client-project +``` + +## Build a Server with ExpressJS + +[ExpressJS](https://expressjs.com/) is a lightweight web framework that can serve http requests in Node. First, install the dev dependencies: + +```bash +npm install --save-dev @nguniversal/express-engine @nguniversal/module-map-ngfactory-loader express webpack-cli ts-loader ws xhr2 +``` + +Create a file called `server.ts` in the root of you project. + +```ts +// These are important and needed before anything else +import 'zone.js/dist/zone-node'; +import 'reflect-metadata'; + +import { enableProdMode } from '@angular/core'; +import { ngExpressEngine } from '@nguniversal/express-engine'; +import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader'; + +import * as express from 'express'; +import { join } from 'path'; +import { readFileSync } from 'fs'; + +// Polyfills required for Firebase +(global as any).WebSocket = require('ws'); +(global as any).XMLHttpRequest = require('xhr2'); + +// Faster renders in prod mode +enableProdMode(); + +// Export our express server +export const app = express(); + +const DIST_FOLDER = join(process.cwd(), 'dist'); +const APP_NAME = 'YOUR_PROJECT_NAME'; // TODO: replace me! + +const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require(`./dist/${APP_NAME}-server/main`); + +// index.html template +const template = readFileSync(join(DIST_FOLDER, APP_NAME, 'index.html')).toString(); + +app.engine('html', ngExpressEngine({ + bootstrap: AppServerModuleNgFactory, + providers: [ + provideModuleMap(LAZY_MODULE_MAP) + ] +})); + +app.set('view engine', 'html'); +app.set('views', join(DIST_FOLDER, APP_NAME)); + +// Serve static files +app.get('*.*', express.static(join(DIST_FOLDER, APP_NAME))); + +// All regular routes use the Universal engine +app.get('*', (req, res) => { + res.render(join(DIST_FOLDER, APP_NAME, 'index.html'), { req }); +}); + +// If we're not in the Cloud Functions environment, spin up a Node server +if (!process.env.FUNCTION_NAME) { + const PORT = process.env.PORT || 4000; + app.listen(PORT, () => { + console.log(`Node server listening on http://localhost:${PORT}`); + }); +} +``` + +## Add a Webpack Config for the Express Server + +Create a new file named `webpack.server.config.js` to bundle the express app from previous step. + + +```js +const path = require('path'); +const webpack = require('webpack'); + +const APP_NAME = 'YOUR_PROJECT_NAME'; // TODO: replace me! + +module.exports = { + entry: { server: './server.ts' }, + resolve: { extensions: ['.js', '.ts'] }, + mode: 'development', + target: 'node', + externals: [ + /* Firebase has some troubles being webpacked when in + in the Node environment, let's skip it. + Note: you may need to exclude other dependencies depending + on your project. */ + /^firebase/ + ], + output: { + // Export a UMD of the webpacked server.ts & deps, for + // rendering in Cloud Functions + path: path.join(__dirname, `dist/${APP_NAME}-webpack`), + library: 'app', + libraryTarget: 'umd', + filename: '[name].js' + }, + module: { + rules: [ + { test: /\.ts$/, loader: 'ts-loader' } + ] + }, + plugins: [ + new webpack.ContextReplacementPlugin( + /(.+)?angular(\\|\/)core(.+)?/, + path.join(__dirname, 'src'), // location of your src + {} // a map of your routes + ), + new webpack.ContextReplacementPlugin( + /(.+)?express(\\|\/)(.+)?/, + path.join(__dirname, 'src'), + {} + ) + ] +} +``` + +## Build Scripts + +Update your `package.json` with the following build scripts, replacing `YOUR_PROJECT_NAME` with the name of your project. + +```js +"scripts": { + // ... omitted + "build": "ng build && npm run build:ssr", + "build:ssr": "ng run YOUR_PROJECT_NAME:server && npm run webpack:ssr", + "webpack:ssr": "webpack --config webpack.server.config.js", + "serve:ssr": "node dist/YOUR_PROJECT_NAME-webpack/server.js" +}, +``` + +Test your app locally by running `npm run build && npm run serve:ssr`. diff --git a/site/src/universal/index.md b/site/src/universal/index.md new file mode 100644 index 000000000..b7d4c6329 --- /dev/null +++ b/site/src/universal/index.md @@ -0,0 +1,5 @@ +--- +eleventyNavigation: + key: Universal + order: 11 +--- diff --git a/site/src/universal/prerendering.md b/site/src/universal/prerendering.md new file mode 100644 index 000000000..60cd4360b --- /dev/null +++ b/site/src/universal/prerendering.md @@ -0,0 +1,80 @@ +--- +title: Prerendering +eleventyNavigation: + key: Prerendering + parent: Universal +--- + +## Prerendering Universal sites + +Prerendering a Universal application allows us to generate the HTML before the user requests it; increasing performance and decreasing cost. Let's configure your application to prerender and staticly serve it's most commonly accessed routes on Firebase Hosting. + +First create a `static.paths.js` in your project root, which lists the URLs you'd want to prerender: + +```js +export default [ + '/', + '/another_path', + '/yet_another_path' +]; +``` + +Install `mkdir-recursive` to make the next step a little easier: + +```bash +npm i --save-dev mkdir-recursive +``` + +Now replace the listener in your `server.ts` with the following: + +```ts +import { readFileSync, writeFileSync, existsSync } from 'fs'; +import { renderModuleFactory } from '@angular/platform-server'; +import { mkdirSync } from 'mkdir-recursive'; + +if (process.env.PRERENDER) { + + const routes = require('./static.paths').default; + Promise.all( + routes.map(route => + renderModuleFactory(AppServerModuleNgFactory, { + document: template, + url: route, + extraProviders: [ + provideModuleMap(LAZY_MODULE_MAP) + ] + }).then(html => [route, html]) + ) + ).then(results => { + results.forEach(([route, html]) => { + const fullPath = join('./public', route); + if (!existsSync(fullPath)) { mkdirSync(fullPath); } + writeFileSync(join(fullPath, 'index.html'), html); + }); + process.exit(); + }); + +} else if (!process.env.FUNCTION_NAME) { + + // If we're not in the Cloud Functions environment, spin up a Node server + const PORT = process.env.PORT || 4000; + app.listen(PORT, () => { + console.log(`Node server listening on http://localhost:${PORT}`); + }); + +} +``` + +Now if the `PRERENDER` environment variable is passed any value, instead of serving your application it will iterate over the paths in `static.paths.js`, render them, and write them to your `public` directory. *You could always make this a seperate script.* + +Finally make some modifications to your `package.json`, to prerender your content when you build: + +```js +"scripts": { + // ... omitted + "build": "ng build && npm run copy:hosting && npm run build:functions && npm run prerender:ssr", + "prerender:ssr": "PRERENDER=1 node dist/YOUR_PROJECT_NAME-webpack/server.js", +}, +``` + +Now when you run `npm run build` the prerendered content should be available in your `/public` directory, ready for deployment on Firebase Hosting. diff --git a/site/src/universal/universal.11tydata.json b/site/src/universal/universal.11tydata.json new file mode 100644 index 000000000..b924da1f3 --- /dev/null +++ b/site/src/universal/universal.11tydata.json @@ -0,0 +1,4 @@ +{ + "layout": "guide.njk", + "tags": "guides" +} diff --git a/src/ai/ai.module.ts b/src/ai/ai.module.ts new file mode 100644 index 000000000..444fd67b3 --- /dev/null +++ b/src/ai/ai.module.ts @@ -0,0 +1,79 @@ +import { + EnvironmentProviders, + InjectionToken, + Injector, + NgModule, + NgZone, + Optional, + makeEnvironmentProviders, +} from '@angular/core'; +import { VERSION, ÉĩAngularFireSchedulers, ÉĩgetDefaultInstanceOf } from '@angular/fire'; +import { FirebaseApp, FirebaseApps } from '@angular/fire/app'; +import { AppCheckInstances } from '@angular/fire/app-check'; +import { AI as FirebaseAI } from 'firebase/ai'; +import { registerVersion } from 'firebase/app'; +import { AI, AIInstances, AI_PROVIDER_NAME } from './ai'; + +export const PROVIDED_AI_INSTANCES = new InjectionToken('angularfire2.ai-instances'); + +export function defaultAIInstanceFactory(provided: FirebaseAI[]|undefined, defaultApp: FirebaseApp) { + const defaultAI = ÉĩgetDefaultInstanceOf(AI_PROVIDER_NAME, provided, defaultApp); + return defaultAI && new AI(defaultAI); +} + +export function AIInstanceFactory(fn: (injector: Injector) => FirebaseAI) { + return (zone: NgZone, injector: Injector) => { + const ai = zone.runOutsideAngular(() => fn(injector)); + return new AI(ai); + }; +} + +const AI_INSTANCES_PROVIDER = { + provide: AIInstances, + deps: [ + [new Optional(), PROVIDED_AI_INSTANCES ], + ] +}; + +const DEFAULT_AI_INSTANCE_PROVIDER = { + provide: AI, + useFactory: defaultAIInstanceFactory, + deps: [ + [new Optional(), PROVIDED_AI_INSTANCES ], + FirebaseApp, + ] +}; + +@NgModule({ + providers: [ + DEFAULT_AI_INSTANCE_PROVIDER, + AI_INSTANCES_PROVIDER, + ] +}) +export class AIModule { + constructor() { + registerVersion('angularfire', VERSION.full, 'ai'); + } +} + +export function provideAI(fn: (injector: Injector) => FirebaseAI, ...deps: any[]): EnvironmentProviders { + registerVersion('angularfire', VERSION.full, 'ai'); + + return makeEnvironmentProviders([ + DEFAULT_AI_INSTANCE_PROVIDER, + AI_INSTANCES_PROVIDER, + { + provide: PROVIDED_AI_INSTANCES, + useFactory: AIInstanceFactory(fn), + multi: true, + deps: [ + NgZone, + Injector, + ÉĩAngularFireSchedulers, + FirebaseApps, + [new Optional(), AppCheckInstances ], + ...deps, + ] + } + ]); +} diff --git a/src/ai/ai.spec.ts b/src/ai/ai.spec.ts new file mode 100644 index 000000000..9b6fc1008 --- /dev/null +++ b/src/ai/ai.spec.ts @@ -0,0 +1,38 @@ +import { TestBed } from '@angular/core/testing'; +import { AI, getAI, provideAI } from '@angular/fire/ai'; +import { FirebaseApp, getApp, initializeApp, provideFirebaseApp } from '@angular/fire/app'; +import { COMMON_CONFIG } from '../test-config'; +import { rando } from '../utils'; + +describe('AI', () => { + let app: FirebaseApp; + let ai: AI; + let providedAI: AI; + let appName: string; + + describe('single injection', () => { + + beforeEach(() => { + appName = rando(); + TestBed.configureTestingModule({ + providers: [ + provideFirebaseApp(() => initializeApp(COMMON_CONFIG, appName)), + provideAI(() => { + providedAI = getAI(getApp(appName)); + return providedAI; + }), + ], + }); + app = TestBed.inject(FirebaseApp); + ai = TestBed.inject(AI); + }); + + it('should be injectable', () => { + expect(providedAI).toBeTruthy(); + expect(ai).toEqual(providedAI); + expect(ai.app).toEqual(app); + }); + + }); + +}); diff --git a/src/ai/ai.ts b/src/ai/ai.ts new file mode 100644 index 000000000..5b33bf9b4 --- /dev/null +++ b/src/ai/ai.ts @@ -0,0 +1,30 @@ +import { ÉĩgetAllInstancesOf } from '@angular/fire'; +import { AI as FirebaseAI } from 'firebase/ai'; +import { from, timer } from 'rxjs'; +import { concatMap, distinct } from 'rxjs/operators'; + +// see notes in core/firebase.app.module.ts for why we're building the class like this + +export interface AI extends FirebaseAI {} + +export class AI { + constructor(ai: FirebaseAI) { + return ai; + } +} + +export const AI_PROVIDER_NAME = 'AI'; + + +export interface AIInstances extends Array {} + +export class AIInstances { + constructor() { + return ÉĩgetAllInstancesOf(AI_PROVIDER_NAME); + } +} + +export const AIInstance$ = timer(0, 300).pipe( + concatMap(() => from(ÉĩgetAllInstancesOf(AI_PROVIDER_NAME))), + distinct(), +); diff --git a/src/ai/firebase.ts b/src/ai/firebase.ts new file mode 100644 index 000000000..9ca7d8e6a --- /dev/null +++ b/src/ai/firebase.ts @@ -0,0 +1,16 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +export * from 'firebase/ai'; +import { ÉĩzoneWrap } from '@angular/fire'; +import { + getAI as _getAI, + getGenerativeModel as _getGenerativeModel, + getImagenModel as _getImagenModel, + getLiveGenerativeModel as _getLiveGenerativeModel, + startAudioConversation as _startAudioConversation +} from 'firebase/ai'; + +export const getAI = ÉĩzoneWrap(_getAI, true); +export const getGenerativeModel = ÉĩzoneWrap(_getGenerativeModel, true); +export const getImagenModel = ÉĩzoneWrap(_getImagenModel, true); +export const getLiveGenerativeModel = ÉĩzoneWrap(_getLiveGenerativeModel, true); +export const startAudioConversation = ÉĩzoneWrap(_startAudioConversation, true); diff --git a/src/ai/ng-package.json b/src/ai/ng-package.json new file mode 100644 index 000000000..d3032da35 --- /dev/null +++ b/src/ai/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/ai/package.json b/src/ai/package.json new file mode 100644 index 000000000..8dce418a5 --- /dev/null +++ b/src/ai/package.json @@ -0,0 +1,3 @@ +{ + "$schema": "../../node_modules/ng-packagr/package.schema.json" +} diff --git a/src/ai/public_api.ts b/src/ai/public_api.ts new file mode 100644 index 000000000..aee859b99 --- /dev/null +++ b/src/ai/public_api.ts @@ -0,0 +1,3 @@ +export { AI, AIInstances, AIInstance$ } from './ai'; +export { provideAI, AIModule } from './ai.module'; +export * from './firebase'; diff --git a/src/analytics/analytics.module.ts b/src/analytics/analytics.module.ts index 3c104d5d3..f2552ffc5 100644 --- a/src/analytics/analytics.module.ts +++ b/src/analytics/analytics.module.ts @@ -1,18 +1,88 @@ -import { NgModule, Optional } from '@angular/core'; -import { ScreenTrackingService, UserTrackingService } from './analytics.service'; -import { AngularFireAnalytics } from './analytics'; +import { isPlatformBrowser } from '@angular/common'; +import { + EnvironmentProviders, + InjectionToken, + Injector, + NgModule, + NgZone, + Optional, + PLATFORM_ID, + makeEnvironmentProviders, +} from '@angular/core'; +import { VERSION, ÉĩAngularFireSchedulers, ÉĩgetDefaultInstanceOf } from '@angular/fire'; +import { FirebaseApp, FirebaseApps } from '@angular/fire/app'; +import { Analytics as FirebaseAnalytics } from 'firebase/analytics'; +import { registerVersion } from 'firebase/app'; +import { ANALYTICS_PROVIDER_NAME, Analytics, AnalyticsInstances } from './analytics'; +import { ScreenTrackingService } from './screen-tracking.service'; +import { UserTrackingService } from './user-tracking.service'; + +export const PROVIDED_ANALYTICS_INSTANCES = new InjectionToken('angularfire2.analytics-instances'); + +export function defaultAnalyticsInstanceFactory(provided: FirebaseAnalytics[]|undefined, defaultApp: FirebaseApp, platformId: object) { + if (!isPlatformBrowser(platformId)) { return null; } + const defaultAnalytics = ÉĩgetDefaultInstanceOf(ANALYTICS_PROVIDER_NAME, provided, defaultApp); + return defaultAnalytics && new Analytics(defaultAnalytics); +} + +export function analyticsInstanceFactory(fn: (injector: Injector) => FirebaseAnalytics) { + return (zone: NgZone, injector: Injector, platformId: object) => { + if (!isPlatformBrowser(platformId)) { return null; } + const analytics = zone.runOutsideAngular(() => fn(injector)); + return new Analytics(analytics); + }; +} + +const ANALYTICS_INSTANCES_PROVIDER = { + provide: AnalyticsInstances, + deps: [ + [new Optional(), PROVIDED_ANALYTICS_INSTANCES ], + ] +}; + +const DEFAULT_ANALYTICS_INSTANCE_PROVIDER = { + provide: Analytics, + useFactory: defaultAnalyticsInstanceFactory, + deps: [ + [new Optional(), PROVIDED_ANALYTICS_INSTANCES ], + FirebaseApp, + PLATFORM_ID, + ] +}; @NgModule({ - providers: [ AngularFireAnalytics ] + providers: [ + DEFAULT_ANALYTICS_INSTANCE_PROVIDER, + ANALYTICS_INSTANCES_PROVIDER + ] }) -export class AngularFireAnalyticsModule { +export class AnalyticsModule { constructor( - analytics: AngularFireAnalytics, - @Optional() screenTracking: ScreenTrackingService, - @Optional() userTracking: UserTrackingService + @Optional() _screenTrackingService: ScreenTrackingService, + @Optional() _userTrackingService: UserTrackingService, ) { - // calling anything on analytics will eagerly load the SDK - // tslint:disable-next-line:no-unused-expression - analytics.app; + registerVersion('angularfire', VERSION.full, 'analytics'); } } + +export function provideAnalytics(fn: (injector: Injector) => FirebaseAnalytics, ...deps: any[]): EnvironmentProviders { + registerVersion('angularfire', VERSION.full, 'analytics'); + + return makeEnvironmentProviders([ + DEFAULT_ANALYTICS_INSTANCE_PROVIDER, + ANALYTICS_INSTANCES_PROVIDER, + { + provide: PROVIDED_ANALYTICS_INSTANCES, + useFactory: analyticsInstanceFactory(fn), + multi: true, + deps: [ + NgZone, + Injector, + PLATFORM_ID, + ÉĩAngularFireSchedulers, + FirebaseApps, + ...deps, + ], + }, + ]); +} diff --git a/src/analytics/analytics.service.ts b/src/analytics/analytics.service.ts deleted file mode 100644 index ea00a572b..000000000 --- a/src/analytics/analytics.service.ts +++ /dev/null @@ -1,229 +0,0 @@ -import { - ComponentFactoryResolver, - Inject, - Injectable, - Injector, - NgModuleFactory, - NgZone, - OnDestroy, - Optional, - PLATFORM_ID -} from '@angular/core'; -import { from, Observable, of, Subscription } from 'rxjs'; -import { filter, groupBy, map, mergeMap, observeOn, pairwise, startWith, switchMap, tap, withLatestFrom } from 'rxjs/operators'; -import { ActivationEnd, NavigationEnd, Router, ROUTES } from '@angular/router'; -import { ÉĩAngularFireSchedulers } from '@angular/fire'; -import { AngularFireAnalytics, DEBUG_MODE } from './analytics'; -import { User } from 'firebase/app'; -import { Title } from '@angular/platform-browser'; -import { isPlatformBrowser, isPlatformServer } from '@angular/common'; - -const FIREBASE_EVENT_ORIGIN_KEY = 'firebase_event_origin'; -const FIREBASE_PREVIOUS_SCREEN_CLASS_KEY = 'firebase_previous_class'; -const FIREBASE_PREVIOUS_SCREEN_INSTANCE_ID_KEY = 'firebase_previous_id'; -const FIREBASE_PREVIOUS_SCREEN_NAME_KEY = 'firebase_previous_screen'; -const FIREBASE_SCREEN_CLASS_KEY = 'firebase_screen_class'; -const FIREBASE_SCREEN_INSTANCE_ID_KEY = 'firebase_screen_id'; -const FIREBASE_SCREEN_NAME_KEY = 'firebase_screen'; -const OUTLET_KEY = 'outlet'; -const PAGE_PATH_KEY = 'page_path'; -const PAGE_TITLE_KEY = 'page_title'; -const SCREEN_CLASS_KEY = 'screen_class'; -const SCREEN_NAME_KEY = 'screen_name'; - -const SCREEN_VIEW_EVENT = 'screen_view'; -const EVENT_ORIGIN_AUTO = 'auto'; -const DEFAULT_SCREEN_CLASS = '???'; -const NG_PRIMARY_OUTLET = 'primary'; -const SCREEN_INSTANCE_DELIMITER = '#'; - -const ANNOTATIONS = '__annotations__'; - - -// this is an INT64 in iOS/Android but use INT32 cause javascript -let nextScreenInstanceID = Math.floor(Math.random() * (2 ** 32 - 1)) - 2 ** 31; - -const knownScreenInstanceIDs: { [key: string]: number } = {}; - -const getScreenInstanceID = (params: { [key: string]: any }) => { - // unique the screen class against the outlet name - const screenInstanceKey = [ - params[SCREEN_CLASS_KEY], - params[OUTLET_KEY] - ].join(SCREEN_INSTANCE_DELIMITER); - if (knownScreenInstanceIDs.hasOwnProperty(screenInstanceKey)) { - return knownScreenInstanceIDs[screenInstanceKey]; - } else { - const ret = nextScreenInstanceID++; - knownScreenInstanceIDs[screenInstanceKey] = ret; - return ret; - } -}; - -@Injectable({ - providedIn: 'any' -}) -export class ScreenTrackingService implements OnDestroy { - - private disposable: Subscription | undefined; - - constructor( - analytics: AngularFireAnalytics, - @Optional() router: Router, - @Optional() title: Title, - componentFactoryResolver: ComponentFactoryResolver, - // tslint:disable-next-line:ban-types - @Inject(PLATFORM_ID) platformId: Object, - @Optional() @Inject(DEBUG_MODE) debugModeEnabled: boolean | null, - zone: NgZone, - injector: Injector - ) { - if (!router || !isPlatformBrowser(platformId)) { - return this; - } - zone.runOutsideAngular(() => { - const activationEndEvents = router.events.pipe(filter(e => e instanceof ActivationEnd)); - const navigationEndEvents = router.events.pipe(filter(e => e instanceof NavigationEnd)); - this.disposable = navigationEndEvents.pipe( - withLatestFrom(activationEndEvents), - switchMap(([navigationEnd, activationEnd]) => { - // SEMVER: start using optional chains and nullish coalescing once we support newer typescript - const pagePath = navigationEnd.url; - const screenName = activationEnd.snapshot.routeConfig && activationEnd.snapshot.routeConfig.path || pagePath; - const params = { - [SCREEN_NAME_KEY]: screenName, - [PAGE_PATH_KEY]: pagePath, - [FIREBASE_EVENT_ORIGIN_KEY]: EVENT_ORIGIN_AUTO, - [FIREBASE_SCREEN_NAME_KEY]: screenName, - [OUTLET_KEY]: activationEnd.snapshot.outlet - }; - if (title) { - params[PAGE_TITLE_KEY] = title.getTitle(); - } - const component = activationEnd.snapshot.component; - const routeConfig = activationEnd.snapshot.routeConfig; - const loadChildren = routeConfig && routeConfig.loadChildren; - // TODO figure out how to handle minification - if (typeof loadChildren === 'string') { - // SEMVER: this is the older lazy load style "./path#ClassName", drop this when we drop old ng - // TODO is it worth seeing if I can look up the component factory selector from the module name? - // it's lazy so it's not registered with componentFactoryResolver yet... seems a pain for a depreciated style - return of({ ...params, [SCREEN_CLASS_KEY]: loadChildren.split('#')[1] }); - } else if (typeof component === 'string') { - return of({ ...params, [SCREEN_CLASS_KEY]: component }); - } else if (component) { - const componentFactory = componentFactoryResolver.resolveComponentFactory(component); - return of({ ...params, [SCREEN_CLASS_KEY]: componentFactory.selector }); - } else if (loadChildren) { - const loadedChildren = loadChildren(); - const loadedChildren$: Observable = (loadedChildren instanceof Observable) ? - loadedChildren : - from(Promise.resolve(loadedChildren)); - return loadedChildren$.pipe( - map(lazyModule => { - if (lazyModule instanceof NgModuleFactory) { - // AOT create an injector - const moduleRef = lazyModule.create(injector); - // INVESTIGATE is this the right way to get at the matching route? - const routes = moduleRef.injector.get(ROUTES); - const component = routes[0][0].component; // should i just be grabbing 0-0 here? - try { - const componentFactory = moduleRef.componentFactoryResolver.resolveComponentFactory(component); - return { ...params, [SCREEN_CLASS_KEY]: componentFactory.selector }; - } catch (_) { - return { ...params, [SCREEN_CLASS_KEY]: DEFAULT_SCREEN_CLASS }; - } - } else { - // JIT look at the annotations - // INVESTIGATE are there public APIs for this stuff? - const declarations = [].concat.apply([], (lazyModule[ANNOTATIONS] || []).map((f: any) => f.declarations)); - const selectors = [].concat.apply([], declarations.map((c: any) => (c[ANNOTATIONS] || []).map((f: any) => f.selector))); - // should I just be grabbing the selector like this or should i match against the route component? - // const routerModule = lazyModule.ngInjectorDef.imports.find(i => i.ngModule && ....); - // const route = routerModule.providers[0].find(p => p.provide == ROUTES).useValue[0]; - return { ...params, [SCREEN_CLASS_KEY]: selectors[0] || DEFAULT_SCREEN_CLASS }; - } - }) - ); - } else { - return of({ ...params, [SCREEN_CLASS_KEY]: DEFAULT_SCREEN_CLASS }); - } - }), - map(params => ({ - [FIREBASE_SCREEN_CLASS_KEY]: params[SCREEN_CLASS_KEY], - [FIREBASE_SCREEN_INSTANCE_ID_KEY]: getScreenInstanceID(params), - ...params - })), - tap(params => { - // TODO perhaps I can be smarter about this, bubble events up to the nearest outlet? - if (params[OUTLET_KEY] === NG_PRIMARY_OUTLET) { - analytics.setCurrentScreen(params[SCREEN_NAME_KEY]); - analytics.updateConfig({ - [PAGE_PATH_KEY]: params[PAGE_PATH_KEY], - [SCREEN_CLASS_KEY]: params[SCREEN_CLASS_KEY] - }); - if (title) { - analytics.updateConfig({ [PAGE_TITLE_KEY]: params[PAGE_TITLE_KEY] }); - } - } - }), - groupBy(params => params[OUTLET_KEY]), - // tslint:disable-next-line - mergeMap(group => group.pipe(startWith(undefined), pairwise())), - map(([prior, current]) => prior ? { - [FIREBASE_PREVIOUS_SCREEN_CLASS_KEY]: prior[SCREEN_CLASS_KEY], - [FIREBASE_PREVIOUS_SCREEN_NAME_KEY]: prior[SCREEN_NAME_KEY], - [FIREBASE_PREVIOUS_SCREEN_INSTANCE_ID_KEY]: prior[FIREBASE_SCREEN_INSTANCE_ID_KEY], - ...current - } : current), - // tslint:disable-next-line:no-console - tap(params => debugModeEnabled && console.info(SCREEN_VIEW_EVENT, params)), - tap(params => zone.runOutsideAngular(() => analytics.logEvent(SCREEN_VIEW_EVENT, params))) - ).subscribe(); - }); - } - - ngOnDestroy() { - if (this.disposable) { - this.disposable.unsubscribe(); - } - } - -} - -@Injectable({ - providedIn: 'any' -}) -export class UserTrackingService implements OnDestroy { - - private disposable: Subscription | undefined; - - // TODO a user properties injector - constructor( - analytics: AngularFireAnalytics, - zone: NgZone, - // tslint:disable-next-line:ban-types - @Inject(PLATFORM_ID) platformId: Object - ) { - const schedulers = new ÉĩAngularFireSchedulers(zone); - - if (!isPlatformServer(platformId)) { - zone.runOutsideAngular(() => { - // @ts-ignore zap the import in the UMD - this.disposable = from(import('firebase/auth')).pipe( - observeOn(schedulers.outsideAngular), - switchMap(() => analytics.app), - map(app => app.auth()), - switchMap(auth => new Observable(auth.onAuthStateChanged.bind(auth))), - switchMap(user => analytics.setUserId(user ? user.uid : null)) - ).subscribe(); - }); - } - } - - ngOnDestroy() { - if (this.disposable) { - this.disposable.unsubscribe(); - } - } -} diff --git a/src/analytics/analytics.spec.ts b/src/analytics/analytics.spec.ts index 9804f993a..f2f6c8c72 100644 --- a/src/analytics/analytics.spec.ts +++ b/src/analytics/analytics.spec.ts @@ -1,36 +1,38 @@ import { TestBed } from '@angular/core/testing'; -import { AngularFireModule, FirebaseApp } from '@angular/fire'; -import { AngularFireAnalytics, AngularFireAnalyticsModule } from './public_api'; -import { COMMON_CONFIG } from '../test-config'; -import { rando } from '../firestore/utils.spec'; +import { Analytics, getAnalytics, provideAnalytics } from '@angular/fire/analytics'; +import { FirebaseApp, getApp, initializeApp, provideFirebaseApp } from '@angular/fire/app'; +import { COMMON_CONFIG_TOO } from '../test-config'; +import { rando } from '../utils'; - -describe('AngularFireAnalytics', () => { +describe('Analytics', () => { let app: FirebaseApp; - let analytics: AngularFireAnalytics; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - AngularFireModule.initializeApp(COMMON_CONFIG, rando()), - AngularFireAnalyticsModule - ] + let analytics: Analytics; + let providedAnalytics: Analytics; + let appName: string; + + describe('single injection', () => { + + beforeEach(() => { + appName = rando(); + TestBed.configureTestingModule({ + providers: [ + provideFirebaseApp(() => initializeApp(COMMON_CONFIG_TOO, appName)), + provideAnalytics(() => { + providedAnalytics = getAnalytics(getApp(appName)); + return providedAnalytics; + }), + ], + }); + app = TestBed.inject(FirebaseApp); + analytics = TestBed.inject(Analytics); }); - app = TestBed.inject(FirebaseApp); - analytics = TestBed.inject(AngularFireAnalytics); - }); - - afterEach(() => { - app.delete(); - }); - - it('should be exist', () => { - expect(analytics instanceof AngularFireAnalytics).toBe(true); - }); + it('should be injectable', () => { + expect(providedAnalytics).toBeTruthy(); + expect(analytics).toEqual(providedAnalytics); + expect(analytics.app).toEqual(app); + }); - it('should have the Firebase Functions instance', () => { - expect(analytics.app).toBeDefined(); }); }); diff --git a/src/analytics/analytics.ts b/src/analytics/analytics.ts index 291d93656..9c394bb98 100644 --- a/src/analytics/analytics.ts +++ b/src/analytics/analytics.ts @@ -1,125 +1,30 @@ -import { Inject, Injectable, InjectionToken, NgZone, Optional, PLATFORM_ID } from '@angular/core'; -import { EMPTY, Observable, of } from 'rxjs'; -import { isPlatformBrowser } from '@angular/common'; -import { map, tap, shareReplay, switchMap, observeOn } from 'rxjs/operators'; -import { - FirebaseAppConfig, - FirebaseOptions, - ÉĩAngularFireSchedulers, - ÉĩlazySDKProxy, - FIREBASE_OPTIONS, - FIREBASE_APP_NAME, - ÉĩfirebaseAppFactory, - ÉĩPromiseProxy -} from '@angular/fire'; -import { analytics } from 'firebase/app'; -import firebase from 'firebase/app'; - -export interface Config { - [key: string]: any; -} - -export const COLLECTION_ENABLED = new InjectionToken('angularfire2.analytics.analyticsCollectionEnabled'); -export const APP_VERSION = new InjectionToken('angularfire2.analytics.appVersion'); -export const APP_NAME = new InjectionToken('angularfire2.analytics.appName'); -export const DEBUG_MODE = new InjectionToken('angularfire2.analytics.debugMode'); -export const CONFIG = new InjectionToken('angularfire2.analytics.config'); - -const APP_NAME_KEY = 'app_name'; -const APP_VERSION_KEY = 'app_version'; -const DEBUG_MODE_KEY = 'debug_mode'; -const ANALYTICS_ID_FIELD = 'measurementId'; -const GTAG_CONFIG_COMMAND = 'config'; -const GTAG_FUNCTION_NAME = 'gtag'; -const DATA_LAYER_NAME = 'dataLayer'; - -export interface AngularFireAnalytics extends ÉĩPromiseProxy { -} - -let gtag: (...args: any[]) => void; -let analyticsInitialized: Promise; -const analyticsInstanceCache: { [key: string]: Observable } = {}; - -@Injectable({ - providedIn: 'any' -}) -export class AngularFireAnalytics { - - async updateConfig(config: Config) { - await analyticsInitialized; - gtag(GTAG_CONFIG_COMMAND, this.options[ANALYTICS_ID_FIELD], { ...config, update: true }); +import { ÉĩgetAllInstancesOf } from '@angular/fire'; +import { Analytics as FirebaseAnalytics } from 'firebase/analytics'; +import { from, timer } from 'rxjs'; +import { concatMap, distinct } from 'rxjs/operators'; + +// see notes in core/firebase.app.module.ts for why we're building the class like this + +export interface Analytics extends FirebaseAnalytics {} + +export class Analytics { + constructor(analytics: FirebaseAnalytics) { + return analytics; } +} - constructor( - @Inject(FIREBASE_OPTIONS) private options: FirebaseOptions, - @Optional() @Inject(FIREBASE_APP_NAME) nameOrConfig: string | FirebaseAppConfig | null | undefined, - @Optional() @Inject(COLLECTION_ENABLED) analyticsCollectionEnabled: boolean | null, - @Optional() @Inject(APP_VERSION) providedAppVersion: string | null, - @Optional() @Inject(APP_NAME) providedAppName: string | null, - @Optional() @Inject(DEBUG_MODE) debugModeEnabled: boolean | null, - @Optional() @Inject(CONFIG) providedConfig: Config | null, - // tslint:disable-next-line:ban-types - @Inject(PLATFORM_ID) platformId: Object, - zone: NgZone - ) { - - if (!analyticsInitialized) { - if (isPlatformBrowser(platformId)) { - gtag = (window[GTAG_FUNCTION_NAME] as any) || ((...args: any[]) => { - (window[DATA_LAYER_NAME] as any).push(args); - }); - window[DATA_LAYER_NAME] = window[DATA_LAYER_NAME] || []; - analyticsInitialized = zone.runOutsideAngular(() => - new Promise(resolve => { - window[GTAG_FUNCTION_NAME] = (...args: any[]) => { - if (args[0] === 'js') { - resolve(); - } - gtag(...args); - }; - }) - ); - } else { - gtag = () => { - }; - analyticsInitialized = Promise.resolve(); - } - } - - let analytics = analyticsInstanceCache[options[ANALYTICS_ID_FIELD]]; - if (!analytics) { - analytics = of(undefined).pipe( - observeOn(new ÉĩAngularFireSchedulers(zone).outsideAngular), - switchMap(() => isPlatformBrowser(platformId) ? import('firebase/analytics') : EMPTY), - switchMap(() => import('@firebase/analytics')), - tap(analytics => analytics.registerAnalytics && analytics.registerAnalytics(firebase as any)), - map(() => ÉĩfirebaseAppFactory(options, zone, nameOrConfig)), - map(app => app.analytics()), - tap(analytics => { - if (analyticsCollectionEnabled === false) { - analytics.setAnalyticsCollectionEnabled(false); - } - }), - shareReplay({ bufferSize: 1, refCount: false }) - ); - analyticsInstanceCache[options[ANALYTICS_ID_FIELD]] = analytics; - } - - if (providedConfig) { - this.updateConfig(providedConfig); - } - if (providedAppName) { - this.updateConfig({ [APP_NAME_KEY]: providedAppName }); - } - if (providedAppVersion) { - this.updateConfig({ [APP_VERSION_KEY]: providedAppVersion }); - } - if (debugModeEnabled) { - this.updateConfig({ [DEBUG_MODE_KEY]: 1 }); - } +export const ANALYTICS_PROVIDER_NAME = 'analytics'; - return ÉĩlazySDKProxy(this, analytics, zone); + +export interface AnalyticsInstances extends Array {} +export class AnalyticsInstances { + constructor() { + return ÉĩgetAllInstancesOf(ANALYTICS_PROVIDER_NAME); } - } + +export const analyticInstance$ = timer(0, 300).pipe( + concatMap(() => from(ÉĩgetAllInstancesOf(ANALYTICS_PROVIDER_NAME))), + distinct(), +); diff --git a/src/analytics/firebase.ts b/src/analytics/firebase.ts new file mode 100644 index 000000000..2c40e5612 --- /dev/null +++ b/src/analytics/firebase.ts @@ -0,0 +1,30 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +export * from 'firebase/analytics'; +import { ÉĩzoneWrap } from '@angular/fire'; +import { + getAnalytics as _getAnalytics, + getGoogleAnalyticsClientId as _getGoogleAnalyticsClientId, + initializeAnalytics as _initializeAnalytics, + isSupported as _isSupported, + logEvent as _logEvent, + setAnalyticsCollectionEnabled as _setAnalyticsCollectionEnabled, + setConsent as _setConsent, + setCurrentScreen as _setCurrentScreen, + setDefaultEventParameters as _setDefaultEventParameters, + setUserId as _setUserId, + setUserProperties as _setUserProperties, + settings as _settings +} from 'firebase/analytics'; + +export const getAnalytics = ÉĩzoneWrap(_getAnalytics, true); +export const getGoogleAnalyticsClientId = ÉĩzoneWrap(_getGoogleAnalyticsClientId, true); +export const initializeAnalytics = ÉĩzoneWrap(_initializeAnalytics, true); +export const isSupported = ÉĩzoneWrap(_isSupported, false); +export const logEvent = ÉĩzoneWrap(_logEvent, false, 2); +export const setAnalyticsCollectionEnabled = ÉĩzoneWrap(_setAnalyticsCollectionEnabled, true, 2); +export const setConsent = ÉĩzoneWrap(_setConsent, true, 2); +export const setCurrentScreen = ÉĩzoneWrap(_setCurrentScreen, true, 2); +export const setDefaultEventParameters = ÉĩzoneWrap(_setDefaultEventParameters, true, 2); +export const setUserId = ÉĩzoneWrap(_setUserId, true, 2); +export const setUserProperties = ÉĩzoneWrap(_setUserProperties, true, 2); +export const settings = ÉĩzoneWrap(_settings, true); diff --git a/src/analytics/ng-package.json b/src/analytics/ng-package.json new file mode 100644 index 000000000..d3032da35 --- /dev/null +++ b/src/analytics/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/analytics/package.json b/src/analytics/package.json index 9ff024aae..5e8aa1496 100644 --- a/src/analytics/package.json +++ b/src/analytics/package.json @@ -1,11 +1,3 @@ { - "$schema": "../node_modules/ng-packagr/package.schema.json", - "ngPackage": { - "lib": { - "umdModuleIds": { - "firebase/app": "firebase" - }, - "entryFile": "public_api.ts" - } - } + "$schema": "../../node_modules/ng-packagr/package.schema.json" } \ No newline at end of file diff --git a/src/analytics/public_api.ts b/src/analytics/public_api.ts index 8ce1376e6..ed2050781 100644 --- a/src/analytics/public_api.ts +++ b/src/analytics/public_api.ts @@ -1,3 +1,5 @@ -export * from './analytics'; -export * from './analytics.module'; -export * from './analytics.service'; +export { Analytics, AnalyticsInstances, analyticInstance$ } from './analytics'; +export { provideAnalytics, AnalyticsModule } from './analytics.module'; +export * from './firebase'; +export * from './screen-tracking.service'; +export * from './user-tracking.service'; diff --git a/src/analytics/screen-tracking.service.ts b/src/analytics/screen-tracking.service.ts new file mode 100644 index 000000000..924832cc1 --- /dev/null +++ b/src/analytics/screen-tracking.service.ts @@ -0,0 +1,180 @@ +import { ComponentFactoryResolver, Injectable, Injector, NgZone, OnDestroy, Optional } from '@angular/core'; +import { VERSION } from '@angular/fire'; +import { Title } from '@angular/platform-browser'; +import { ActivationEnd, Router, ÉĩEmptyOutletComponent } from '@angular/router'; +import { registerVersion } from 'firebase/app'; +import { Observable, Subscription, of } from 'rxjs'; +import { distinctUntilChanged, filter, groupBy, map, mergeMap, pairwise, startWith, switchMap } from 'rxjs/operators'; +import { Analytics } from './analytics'; +import { isSupported, logEvent } from './firebase'; +import { UserTrackingService } from './user-tracking.service'; + +const FIREBASE_EVENT_ORIGIN_KEY = 'firebase_event_origin'; +const FIREBASE_PREVIOUS_SCREEN_CLASS_KEY = 'firebase_previous_class'; +const FIREBASE_PREVIOUS_SCREEN_INSTANCE_ID_KEY = 'firebase_previous_id'; +const FIREBASE_PREVIOUS_SCREEN_NAME_KEY = 'firebase_previous_screen'; +const FIREBASE_SCREEN_CLASS_KEY = 'firebase_screen_class'; +const FIREBASE_SCREEN_INSTANCE_ID_KEY = 'firebase_screen_id'; +const FIREBASE_SCREEN_NAME_KEY = 'firebase_screen'; +const OUTLET_KEY = 'outlet'; +const PAGE_PATH_KEY = 'page_path'; +const PAGE_TITLE_KEY = 'page_title'; +const SCREEN_CLASS_KEY = 'screen_class'; +const SCREEN_NAME_KEY = 'screen_name'; +const SCREEN_VIEW_EVENT = 'screen_view'; +const EVENT_ORIGIN_AUTO = 'auto'; +const SCREEN_INSTANCE_DELIMITER = '#'; + +// this is an INT64 in iOS/Android but use INT32 cause javascript +let nextScreenInstanceID = Math.floor(Math.random() * (2 ** 32 - 1)) - 2 ** 31; + +const knownScreenInstanceIDs: Record = {}; + +const getScreenInstanceID = (params: Record) => { + // unique the screen class against the outlet name + const screenInstanceKey = [ + params[SCREEN_CLASS_KEY], + params[OUTLET_KEY] + ].join(SCREEN_INSTANCE_DELIMITER); + // eslint-disable-next-line no-prototype-builtins + if (knownScreenInstanceIDs.hasOwnProperty(screenInstanceKey)) { + return knownScreenInstanceIDs[screenInstanceKey]; + } else { + const ret = nextScreenInstanceID++; + knownScreenInstanceIDs[screenInstanceKey] = ret; + return ret; + } +}; + +export const ÉĩscreenViewEvent = ( + router: Router, + title: Title|null, + componentFactoryResolver: ComponentFactoryResolver, +): Observable<{ + [SCREEN_NAME_KEY]: string, + [PAGE_PATH_KEY]: string, + [FIREBASE_EVENT_ORIGIN_KEY]: 'auto', + [FIREBASE_SCREEN_NAME_KEY]: string, + [OUTLET_KEY]: string, + [PAGE_TITLE_KEY]?: string, + [SCREEN_CLASS_KEY]: string, + [FIREBASE_SCREEN_CLASS_KEY]: string, + [FIREBASE_SCREEN_INSTANCE_ID_KEY]: number, + [FIREBASE_PREVIOUS_SCREEN_CLASS_KEY]: string, + [FIREBASE_PREVIOUS_SCREEN_NAME_KEY]: string, + [FIREBASE_PREVIOUS_SCREEN_INSTANCE_ID_KEY]: number, +}> => { + const activationEndEvents = router.events.pipe(filter(e => e instanceof ActivationEnd)); + return activationEndEvents.pipe( + switchMap|null>>(activationEnd => { + // router parseUrl is having trouble with outlets when they're empty + // e.g, /asdf/1(bob://sally:asdf), so put another slash in when empty + const urlTree = router.parseUrl(router.url.replace(/(?:\().+(?:\))/g, a => a.replace('://', ':///'))); + const pagePath = urlTree.root.children[activationEnd.snapshot.outlet]?.toString() || ''; + const actualSnapshot = router.routerState.root.children.map(it => it).find(it => it.outlet === activationEnd.snapshot.outlet); + + if (!actualSnapshot) { + return of(null); + } + + let actualDeep = actualSnapshot; + while (actualDeep.firstChild) { + actualDeep = actualDeep.firstChild; + } + const screenName = actualDeep.pathFromRoot.map(s => s.routeConfig?.path).filter(it => it).join('/') || '/'; + + const params = { + [SCREEN_NAME_KEY]: screenName, + [PAGE_PATH_KEY]: `/${pagePath}`, + [FIREBASE_EVENT_ORIGIN_KEY]: EVENT_ORIGIN_AUTO, + [FIREBASE_SCREEN_NAME_KEY]: screenName, + [OUTLET_KEY]: activationEnd.snapshot.outlet + }; + if (title) { + params[PAGE_TITLE_KEY] = title.getTitle(); + } + + let component = actualSnapshot.component; + if (component) { + if (component === ÉĩEmptyOutletComponent) { + let deepSnapshot = activationEnd.snapshot; + // TODO when might there be mutple children, different outlets? explore + while (deepSnapshot.firstChild) { + deepSnapshot = deepSnapshot.firstChild; + } + component = deepSnapshot.component; + } + } else { + component = activationEnd.snapshot.component; + } + + if (typeof component === 'string') { + return of({ ...params, [SCREEN_CLASS_KEY]: component }); + } else if (component) { + const componentFactory = componentFactoryResolver.resolveComponentFactory(component); + return of({ ...params, [SCREEN_CLASS_KEY]: componentFactory.selector }); + } + // lazy loads cause extra activations, ignore + return of(null); + }), + filter(it => !!it), + map(params => ({ + [FIREBASE_SCREEN_CLASS_KEY]: params[SCREEN_CLASS_KEY], + [FIREBASE_SCREEN_INSTANCE_ID_KEY]: getScreenInstanceID(params), + ...params + })), + groupBy(it => it[OUTLET_KEY]), + mergeMap(it => it.pipe( + distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)), + startWith(undefined), + pairwise(), + map(([prior, current]) => + prior ? { + [FIREBASE_PREVIOUS_SCREEN_CLASS_KEY]: prior[SCREEN_CLASS_KEY], + [FIREBASE_PREVIOUS_SCREEN_NAME_KEY]: prior[SCREEN_NAME_KEY], + [FIREBASE_PREVIOUS_SCREEN_INSTANCE_ID_KEY]: prior[FIREBASE_SCREEN_INSTANCE_ID_KEY], + ...current + } : current + ), + )) + ); +}; + +@Injectable() +export class ScreenTrackingService implements OnDestroy { + + private disposable: Subscription | undefined; + + constructor( + @Optional() router: Router, + @Optional() title: Title, + componentFactoryResolver: ComponentFactoryResolver, + zone: NgZone, + @Optional() userTrackingService: UserTrackingService, + injector: Injector, + ) { + registerVersion('angularfire', VERSION.full, 'screen-tracking'); + // The APP_INITIALIZER that is making isSupported() sync for the sake of convenient DI + // may not be done when services are initialized. Guard the functionality by first ensuring + // that the (global) promise has resolved, then get Analytics from the injector. + isSupported().then(() => { + const analytics = injector.get(Analytics); + if (!router || !analytics) { return; } + zone.runOutsideAngular(() => { + this.disposable = ÉĩscreenViewEvent(router, title, componentFactoryResolver).pipe( + switchMap(async params => { + if (userTrackingService) { await userTrackingService.initialized; } + return logEvent(analytics, SCREEN_VIEW_EVENT, params); + }) + ).subscribe(); + }); + }); + } + + ngOnDestroy() { + if (this.disposable) { + this.disposable.unsubscribe(); + } + } + +} diff --git a/src/analytics/user-tracking.service.ts b/src/analytics/user-tracking.service.ts new file mode 100644 index 000000000..ce40b7c14 --- /dev/null +++ b/src/analytics/user-tracking.service.ts @@ -0,0 +1,45 @@ +import { Injectable, Injector, NgZone, OnDestroy } from '@angular/core'; +import { VERSION } from '@angular/fire'; +import { Auth, authState } from '@angular/fire/auth'; +import { registerVersion } from 'firebase/app'; +import { Subscription } from 'rxjs'; +import { Analytics } from './analytics'; +import { isSupported, setUserId } from './firebase'; + +@Injectable() +export class UserTrackingService implements OnDestroy { + + public readonly initialized: Promise; + private disposables: Subscription[] = []; + + constructor( + auth: Auth, + zone: NgZone, + injector: Injector, + ) { + registerVersion('angularfire', VERSION.full, 'user-tracking'); + let resolveInitialized: () => void; + this.initialized = zone.runOutsideAngular(() => new Promise(resolve => { resolveInitialized = resolve; })); + // The APP_INITIALIZER that is making isSupported() sync for the sake of convenient DI + // may not be done when services are initialized. Guard the functionality by first ensuring + // that the (global) promise has resolved, then get Analytics from the injector. + isSupported().then(() => { + const analytics = injector.get(Analytics); + if (analytics) { + this.disposables = [ + // TODO add credential tracking back in + authState(auth).subscribe(user => { + setUserId(analytics, user?.uid); + resolveInitialized(); + }), + ]; + } else { + resolveInitialized(); + } + }); + } + + ngOnDestroy() { + this.disposables.forEach(it => it.unsubscribe()); + } +} diff --git a/src/app-check/app-check.module.ts b/src/app-check/app-check.module.ts new file mode 100644 index 000000000..b68f848bc --- /dev/null +++ b/src/app-check/app-check.module.ts @@ -0,0 +1,88 @@ +import { isPlatformServer } from '@angular/common'; +import { + EnvironmentProviders, + InjectionToken, + Injector, + NgModule, + NgZone, + Optional, + PLATFORM_ID, + isDevMode, + makeEnvironmentProviders, +} from '@angular/core'; +import { VERSION, ÉĩAngularFireSchedulers, ÉĩgetDefaultInstanceOf } from '@angular/fire'; +import { FirebaseApp, FirebaseApps } from '@angular/fire/app'; +import { registerVersion } from 'firebase/app'; +import { AppCheck as FirebaseAppCheck } from 'firebase/app-check'; +import { APP_CHECK_PROVIDER_NAME, AppCheck, AppCheckInstances } from './app-check'; + +export const PROVIDED_APP_CHECK_INSTANCES = new InjectionToken('angularfire2.app-check-instances'); + +export function defaultAppCheckInstanceFactory(provided: FirebaseAppCheck[]|undefined, defaultApp: FirebaseApp) { + const defaultAppCheck = ÉĩgetDefaultInstanceOf(APP_CHECK_PROVIDER_NAME, provided, defaultApp); + return defaultAppCheck && new AppCheck(defaultAppCheck); +} + +const LOCALHOSTS = ['localhost', '0.0.0.0', '127.0.0.1']; +const isLocalhost = typeof window !== 'undefined' && LOCALHOSTS.includes(window.location.hostname); + +export function appCheckInstanceFactory(fn: (injector: Injector) => FirebaseAppCheck) { + return (zone: NgZone, injector: Injector, platformId: unknown) => { + // Node should use admin token provider, browser devmode and localhost should use debug token + if (!isPlatformServer(platformId) && (isDevMode() || isLocalhost)) { + globalThis.FIREBASE_APPCHECK_DEBUG_TOKEN ??= true; + } + const appCheck = zone.runOutsideAngular(() => fn(injector)); + return new AppCheck(appCheck); + }; +} + +const APP_CHECK_INSTANCES_PROVIDER = { + provide: AppCheckInstances, + deps: [ + [new Optional(), PROVIDED_APP_CHECK_INSTANCES ], + ] +}; + +const DEFAULT_APP_CHECK_INSTANCE_PROVIDER = { + provide: AppCheck, + useFactory: defaultAppCheckInstanceFactory, + deps: [ + [new Optional(), PROVIDED_APP_CHECK_INSTANCES ], + FirebaseApp, + PLATFORM_ID, + ] +}; + +@NgModule({ + providers: [ + DEFAULT_APP_CHECK_INSTANCE_PROVIDER, + APP_CHECK_INSTANCES_PROVIDER, + ] +}) +export class AppCheckModule { + constructor() { + registerVersion('angularfire', VERSION.full, 'app-check'); + } +} + +export function provideAppCheck(fn: (injector: Injector) => FirebaseAppCheck, ...deps: any[]): EnvironmentProviders { + registerVersion('angularfire', VERSION.full, 'app-check'); + return makeEnvironmentProviders([ + DEFAULT_APP_CHECK_INSTANCE_PROVIDER, + APP_CHECK_INSTANCES_PROVIDER, + { + provide: PROVIDED_APP_CHECK_INSTANCES, + useFactory: appCheckInstanceFactory(fn), + multi: true, + deps: [ + NgZone, + Injector, + PLATFORM_ID, + ÉĩAngularFireSchedulers, + FirebaseApps, + ...deps, + ] + } + ]); +} diff --git a/src/app-check/app-check.spec.ts b/src/app-check/app-check.spec.ts new file mode 100644 index 000000000..b51e91e0e --- /dev/null +++ b/src/app-check/app-check.spec.ts @@ -0,0 +1,39 @@ +import { TestBed } from '@angular/core/testing'; +import { FirebaseApp, getApp, initializeApp, provideFirebaseApp } from '@angular/fire/app'; +import { Auth, connectAuthEmulator, getAuth, provideAuth } from '@angular/fire/auth'; +import { COMMON_CONFIG } from '../test-config'; +import { rando } from '../utils'; + +describe('Auth', () => { + let app: FirebaseApp; + let auth: Auth; + let providedAuth: Auth; + let appName: string; + + describe('single injection', () => { + + beforeEach(() => { + appName = rando(); + TestBed.configureTestingModule({ + providers: [ + provideFirebaseApp(() => initializeApp(COMMON_CONFIG, appName)), + provideAuth(() => { + providedAuth = getAuth(getApp(appName)); + connectAuthEmulator(providedAuth, 'http://localhost:9098'); + return providedAuth; + }), + ], + }); + app = TestBed.inject(FirebaseApp); + auth = TestBed.inject(Auth); + }); + + it('should be injectable', () => { + expect(auth).toBeTruthy(); + expect(auth).toEqual(providedAuth); + expect(auth.app).toEqual(app); + }); + + }); + +}); diff --git a/src/app-check/app-check.ts b/src/app-check/app-check.ts new file mode 100644 index 000000000..c24522323 --- /dev/null +++ b/src/app-check/app-check.ts @@ -0,0 +1,30 @@ +import { ÉĩgetAllInstancesOf } from '@angular/fire'; +import { AppCheck as FirebaseAppCheck } from 'firebase/app-check'; +import { from, timer } from 'rxjs'; +import { concatMap, distinct } from 'rxjs/operators'; + +export const APP_CHECK_PROVIDER_NAME = 'app-check'; + +// see notes in core/firebase.app.module.ts for why we're building the class like this + +export interface AppCheck extends FirebaseAppCheck {} + +export class AppCheck { + constructor(appCheck: FirebaseAppCheck) { + return appCheck; + } +} + + +export interface AppCheckInstances extends Array {} + +export class AppCheckInstances { + constructor() { + return ÉĩgetAllInstancesOf(APP_CHECK_PROVIDER_NAME); + } +} + +export const appCheckInstance$ = timer(0, 300).pipe( + concatMap(() => from(ÉĩgetAllInstancesOf(APP_CHECK_PROVIDER_NAME))), + distinct(), +); diff --git a/src/app-check/firebase.ts b/src/app-check/firebase.ts new file mode 100644 index 000000000..bfaefcc29 --- /dev/null +++ b/src/app-check/firebase.ts @@ -0,0 +1,16 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +export * from 'firebase/app-check'; +import { ÉĩzoneWrap } from '@angular/fire'; +import { + getLimitedUseToken as _getLimitedUseToken, + getToken as _getToken, + initializeAppCheck as _initializeAppCheck, + onTokenChanged as _onTokenChanged, + setTokenAutoRefreshEnabled as _setTokenAutoRefreshEnabled +} from 'firebase/app-check'; + +export const getLimitedUseToken = ÉĩzoneWrap(_getLimitedUseToken, true, 2); +export const getToken = ÉĩzoneWrap(_getToken, true); +export const initializeAppCheck = ÉĩzoneWrap(_initializeAppCheck, true); +export const onTokenChanged = ÉĩzoneWrap(_onTokenChanged, true); +export const setTokenAutoRefreshEnabled = ÉĩzoneWrap(_setTokenAutoRefreshEnabled, true); diff --git a/src/app-check/ng-package.json b/src/app-check/ng-package.json new file mode 100644 index 000000000..d3032da35 --- /dev/null +++ b/src/app-check/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/app-check/package.json b/src/app-check/package.json new file mode 100644 index 000000000..5e8aa1496 --- /dev/null +++ b/src/app-check/package.json @@ -0,0 +1,3 @@ +{ + "$schema": "../../node_modules/ng-packagr/package.schema.json" +} \ No newline at end of file diff --git a/src/app-check/public_api.ts b/src/app-check/public_api.ts new file mode 100644 index 000000000..bab774cf3 --- /dev/null +++ b/src/app-check/public_api.ts @@ -0,0 +1,3 @@ +export { AppCheck, appCheckInstance$, AppCheckInstances } from './app-check'; +export { provideAppCheck, AppCheckModule } from './app-check.module'; +export * from './firebase'; diff --git a/src/app/app.module.ts b/src/app/app.module.ts new file mode 100644 index 000000000..ae24bfe4e --- /dev/null +++ b/src/app/app.module.ts @@ -0,0 +1,93 @@ +import { + EnvironmentProviders, + Inject, + InjectionToken, + Injector, + VERSION as NG_VERSION, + NgModule, + NgZone, + Optional, + PLATFORM_ID, + makeEnvironmentProviders, +} from '@angular/core'; +import { VERSION, ÉĩAngularFireSchedulers } from '@angular/fire'; +import { FirebaseApp as IFirebaseApp, getApp, registerVersion } from 'firebase/app'; +import { FirebaseApp, FirebaseApps } from './app'; + +export function defaultFirebaseAppFactory(provided: FirebaseApp[]|undefined) { + // Use the provided app, if there is only one, otherwise fetch the default app + if (provided && provided.length === 1) { return provided[0]; } + return new FirebaseApp(getApp()); +} + +// With FIREBASE_APPS I wanted to capture the default app instance, if it is initialized by +// the reserved URL; ÉĩPROVIDED_FIREBASE_APPS is not for public consumption and serves to ensure that all +// provideFirebaseApp(...) calls are satisfied before FirebaseApp$ or FirebaseApp is resolved +export const PROVIDED_FIREBASE_APPS = new InjectionToken('angularfire2._apps'); + +// Injecting FirebaseApp will now only inject the default Firebase App +// this allows allows beginners to import /__/firebase/init.js to auto initialize Firebase App +// from the reserved URL. +const DEFAULT_FIREBASE_APP_PROVIDER = { + provide: FirebaseApp, + useFactory: defaultFirebaseAppFactory, + deps: [ + [new Optional(), PROVIDED_FIREBASE_APPS ], + ], +}; + +const FIREBASE_APPS_PROVIDER = { + provide: FirebaseApps, + deps: [ + [new Optional(), PROVIDED_FIREBASE_APPS ], + ], +}; + +export function firebaseAppFactory(fn: (injector: Injector) => IFirebaseApp) { + return (zone: NgZone, injector: Injector) => { + const platformId = injector.get(PLATFORM_ID); + registerVersion('angularfire', VERSION.full, 'core'); + registerVersion('angularfire', VERSION.full, 'app'); + // eslint-disable-next-line @typescript-eslint/no-base-to-string + registerVersion('angular', NG_VERSION.full, platformId.toString()); + + const app = zone.runOutsideAngular(() => fn(injector)); + return new FirebaseApp(app); + }; +} + +@NgModule({ + providers: [ + DEFAULT_FIREBASE_APP_PROVIDER, + FIREBASE_APPS_PROVIDER, + ] +}) +export class FirebaseAppModule { + constructor(@Inject(PLATFORM_ID) platformId: object) { + registerVersion('angularfire', VERSION.full, 'core'); + registerVersion('angularfire', VERSION.full, 'app'); + // eslint-disable-next-line @typescript-eslint/no-base-to-string + registerVersion('angular', NG_VERSION.full, platformId.toString()); + } +} + +// Calling initializeApp({ ... }, 'name') multiple times will add more FirebaseApps into the FIREBASE_APPS +// injection scope. This allows developers to more easily work with multiple Firebase Applications. Downside +// is that DI for app name and options doesn't really make sense anymore. +export function provideFirebaseApp(fn: (injector: Injector) => IFirebaseApp, ...deps: any[]): EnvironmentProviders { + return makeEnvironmentProviders([ + DEFAULT_FIREBASE_APP_PROVIDER, + FIREBASE_APPS_PROVIDER, + { + provide: PROVIDED_FIREBASE_APPS, + useFactory: firebaseAppFactory(fn), + multi: true, + deps: [ + NgZone, + Injector, + ÉĩAngularFireSchedulers, + ...deps, + ], + } + ]) +} diff --git a/src/app/app.spec.ts b/src/app/app.spec.ts new file mode 100644 index 000000000..62af613f8 --- /dev/null +++ b/src/app/app.spec.ts @@ -0,0 +1,33 @@ +import { TestBed } from '@angular/core/testing'; +import { FirebaseApp, initializeApp, provideFirebaseApp } from '@angular/fire/app'; +import { COMMON_CONFIG } from '../test-config'; +import { rando } from '../utils'; + +describe('FirebaseApp', () => { + let app: FirebaseApp; + let providedApp: FirebaseApp; + let appName: string; + + describe('single injection', () => { + + beforeEach(() => { + appName = rando(); + TestBed.configureTestingModule({ + providers: [ + provideFirebaseApp(() => { + providedApp = initializeApp(COMMON_CONFIG, appName); + return providedApp; + }) + ], + }); + app = TestBed.inject(FirebaseApp); + }); + + it('should be injectable', () => { + expect(app).toBeTruthy(); + expect(app).toEqual(providedApp); + }); + + }); + +}); diff --git a/src/app/app.ts b/src/app/app.ts new file mode 100644 index 000000000..ac4acbe7d --- /dev/null +++ b/src/app/app.ts @@ -0,0 +1,39 @@ +import { FirebaseApp as IFirebaseApp, getApps } from 'firebase/app'; +import { from, timer } from 'rxjs'; +import { concatMap, distinct } from 'rxjs/operators'; + +// Need to turn the FirebaseApp interface exported by firebase/app into a class +// as types don't work in Angular DI. We want developers to be able to inject FirebaseApp like so +// constructor(app: FirebaseApp) +// the cleanest way to achieve this that I found is to export a new interface and class +// the interface just extends the interface you want to turn into the class. This informs tyepscript +// that the class has all the same methods/properties as the interface you want to extend without +// breaking if Firebase adds/removes APIs in future releases. This was a big problem for @angular/fire +// back when we constructed our own class. Then in the "new class" we just return the FirebaseApp in the +// constructor, this also has the added benefit of Firebase methods taking our DI class without +// casting. E.g, +// constructor(private app: FirebaseApp) { } +// ngOnDestroy() { deleteApp(this.app); } +// + +export interface FirebaseApp extends IFirebaseApp {} + +export class FirebaseApp { + constructor(app: IFirebaseApp) { + return app; + } +} + + +export interface FirebaseApps extends Array {} + +export class FirebaseApps { + constructor() { + return getApps(); + } +} + +export const firebaseApp$ = timer(0, 300).pipe( + concatMap(() => from(getApps())), + distinct(), +); diff --git a/src/app/firebase.ts b/src/app/firebase.ts new file mode 100644 index 000000000..7d9487193 --- /dev/null +++ b/src/app/firebase.ts @@ -0,0 +1,22 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +export * from 'firebase/app'; +import { ÉĩzoneWrap } from '@angular/fire'; +import { + deleteApp as _deleteApp, + getApp as _getApp, + getApps as _getApps, + initializeApp as _initializeApp, + initializeServerApp as _initializeServerApp, + onLog as _onLog, + registerVersion as _registerVersion, + setLogLevel as _setLogLevel +} from 'firebase/app'; + +export const deleteApp = ÉĩzoneWrap(_deleteApp, true); +export const getApp = ÉĩzoneWrap(_getApp, true); +export const getApps = ÉĩzoneWrap(_getApps, true); +export const initializeApp = ÉĩzoneWrap(_initializeApp, true); +export const initializeServerApp = ÉĩzoneWrap(_initializeServerApp, true); +export const onLog = ÉĩzoneWrap(_onLog, true); +export const registerVersion = ÉĩzoneWrap(_registerVersion, true); +export const setLogLevel = ÉĩzoneWrap(_setLogLevel, true); diff --git a/src/app/ng-package.json b/src/app/ng-package.json new file mode 100644 index 000000000..d3032da35 --- /dev/null +++ b/src/app/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/app/package.json b/src/app/package.json new file mode 100644 index 000000000..5e8aa1496 --- /dev/null +++ b/src/app/package.json @@ -0,0 +1,3 @@ +{ + "$schema": "../../node_modules/ng-packagr/package.schema.json" +} \ No newline at end of file diff --git a/src/app/public_api.ts b/src/app/public_api.ts new file mode 100644 index 000000000..e33036709 --- /dev/null +++ b/src/app/public_api.ts @@ -0,0 +1,3 @@ +export { FirebaseApp, FirebaseApps, firebaseApp$ } from './app'; +export { provideFirebaseApp, FirebaseAppModule } from './app.module'; +export * from './firebase'; diff --git a/src/auth-guard/auth-guard.module.ts b/src/auth-guard/auth-guard.module.ts index c107f720e..3d9480545 100644 --- a/src/auth-guard/auth-guard.module.ts +++ b/src/auth-guard/auth-guard.module.ts @@ -1,7 +1,13 @@ import { NgModule } from '@angular/core'; -import { AngularFireAuthGuard } from './auth-guard'; +import { VERSION } from '@angular/fire'; +import { registerVersion } from 'firebase/app'; +import { AuthGuard } from './auth-guard'; @NgModule({ - providers: [ AngularFireAuthGuard ] + providers: [ AuthGuard ] }) -export class AngularFireAuthGuardModule { } +export class AuthGuardModule { + constructor() { + registerVersion('angularfire', VERSION.full, 'auth-guard'); + } +} diff --git a/src/auth-guard/auth-guard.spec.ts b/src/auth-guard/auth-guard.spec.ts index 7c3ed2c45..9c87a3fd3 100644 --- a/src/auth-guard/auth-guard.spec.ts +++ b/src/auth-guard/auth-guard.spec.ts @@ -1,38 +1,47 @@ +import { APP_BASE_HREF } from '@angular/common'; import { TestBed } from '@angular/core/testing'; -import { AngularFireModule, FirebaseApp } from '@angular/fire'; -import { COMMON_CONFIG } from '../test-config'; -import { AngularFireAuthGuard, AngularFireAuthGuardModule } from './public_api'; +import { getApp, initializeApp, provideFirebaseApp } from '@angular/fire/app'; +import { connectAuthEmulator, getAuth, provideAuth } from '@angular/fire/auth'; +import { AuthGuard, AuthGuardModule } from '@angular/fire/auth-guard'; import { Router, RouterModule } from '@angular/router'; -import { APP_BASE_HREF } from '@angular/common'; -import { rando } from '../firestore/utils.spec'; +import { COMMON_CONFIG } from '../test-config'; +import { rando } from '../utils'; -describe('AngularFireAuthGuard', () => { - let app: FirebaseApp; +class TestComponent { } + +describe('AuthGuard', () => { let router: Router; + let appName: string; beforeEach(() => { + appName = rando(); TestBed.configureTestingModule({ imports: [ - AngularFireModule.initializeApp(COMMON_CONFIG, rando()), - AngularFireAuthGuardModule, + AuthGuardModule, RouterModule.forRoot([ - { path: 'a', redirectTo: '/', canActivate: [AngularFireAuthGuard] } + { path: 'a', component: TestComponent, canActivate: [AuthGuard] } ]) ], providers: [ + provideFirebaseApp(() => initializeApp(COMMON_CONFIG, appName)), + provideAuth(() => { + const auth = getAuth(getApp(appName)); + connectAuthEmulator(auth, 'http://localhost:9098'); + return auth; + }), { provide: APP_BASE_HREF, useValue: 'http://localhost:4200/' } ] }); - app = TestBed.inject(FirebaseApp); router = TestBed.inject(Router); }); - afterEach(done => { - app.delete().then(done, done); + it('should be injectable', () => { + expect(AuthGuard).toBeTruthy(); }); - it('should be injectable', () => { + it('router should be valid', () => { expect(router).toBeTruthy(); }); + }); diff --git a/src/auth-guard/auth-guard.ts b/src/auth-guard/auth-guard.ts index 6368d346a..51cff3550 100644 --- a/src/auth-guard/auth-guard.ts +++ b/src/auth-guard/auth-guard.ts @@ -1,78 +1,54 @@ -import { Inject, Injectable, NgZone, Optional } from '@angular/core'; +import { Injectable } from '@angular/core'; +import { Auth, user } from '@angular/fire/auth'; import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; -import { Observable, of, pipe, UnaryFunction } from 'rxjs'; -import { map, observeOn, shareReplay, switchMap, take, tap } from 'rxjs/operators'; -import { User } from 'firebase/app'; -import { - ÉĩAngularFireSchedulers, - FirebaseOptions, - FirebaseAppConfig, - FIREBASE_OPTIONS, - FIREBASE_APP_NAME, - ÉĩfirebaseAppFactory, - ÉĩkeepUnstableUntilFirstFactory -} from '@angular/fire'; +import { User } from 'firebase/auth'; +import { Observable, UnaryFunction, of, pipe } from 'rxjs'; +import { map, switchMap, take } from 'rxjs/operators'; export type AuthPipeGenerator = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot) => AuthPipe; -export type AuthPipe = UnaryFunction, Observable>; +export type AuthPipe = UnaryFunction, Observable>; export const loggedIn: AuthPipe = map(user => !!user); @Injectable({ providedIn: 'any' }) -export class AngularFireAuthGuard implements CanActivate { +export class AuthGuard implements CanActivate { - authState: Observable; - - constructor( - @Inject(FIREBASE_OPTIONS) options: FirebaseOptions, - @Optional() @Inject(FIREBASE_APP_NAME) nameOrConfig: string|FirebaseAppConfig|null|undefined, - zone: NgZone, - private router: Router - ) { - - const schedulers = new ÉĩAngularFireSchedulers(zone); - const keepUnstableUntilFirst = ÉĩkeepUnstableUntilFirstFactory(schedulers); - - const auth = of(undefined).pipe( - observeOn(new ÉĩAngularFireSchedulers(zone).outsideAngular), - switchMap(() => zone.runOutsideAngular(() => import('firebase/auth'))), - tap((it: any) => it), // It seems I need to touch the import for it to do anything... race maybe? - map(() => ÉĩfirebaseAppFactory(options, zone, nameOrConfig)), - map(app => zone.runOutsideAngular(() => app.auth())), - shareReplay({ bufferSize: 1, refCount: false }), - ); - - this.authState = auth.pipe( - switchMap(auth => new Observable(auth.onAuthStateChanged.bind(auth))), - keepUnstableUntilFirst - ); - } + constructor(private router: Router, private auth: Auth) {} canActivate = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot) => { const authPipeFactory = next.data.authGuardPipe as AuthPipeGenerator || (() => loggedIn); - return this.authState.pipe( + return user(this.auth).pipe( take(1), authPipeFactory(next, state), - map(can => typeof can === 'boolean' ? can : this.router.createUrlTree(can as any[])) + map(can => { + if (typeof can === 'boolean') { + return can; + } else if (Array.isArray(can)) { + return this.router.createUrlTree(can); + } else { + // TODO(EdricChan03): Add tests + return this.router.parseUrl(can); + } + }) ); } } export const canActivate = (pipe: AuthPipeGenerator) => ({ - canActivate: [ AngularFireAuthGuard ], data: { authGuardPipe: pipe } + canActivate: [ AuthGuard ], data: { authGuardPipe: pipe } }); - export const isNotAnonymous: AuthPipe = map(user => !!user && !user.isAnonymous); export const idTokenResult = switchMap((user: User|null) => user ? user.getIdTokenResult() : of(null)); export const emailVerified: AuthPipe = map(user => !!user && user.emailVerified); export const customClaims = pipe(idTokenResult, map(idTokenResult => idTokenResult ? idTokenResult.claims : [])); export const hasCustomClaim: (claim: string) => AuthPipe = + // eslint-disable-next-line no-prototype-builtins (claim) => pipe(customClaims, map(claims => claims.hasOwnProperty(claim))); -export const redirectUnauthorizedTo: (redirect: any[]) => AuthPipe = +export const redirectUnauthorizedTo: (redirect: string|any[]) => AuthPipe = (redirect) => pipe(loggedIn, map(loggedIn => loggedIn || redirect)); -export const redirectLoggedInTo: (redirect: any[]) => AuthPipe = +export const redirectLoggedInTo: (redirect: string|any[]) => AuthPipe = (redirect) => pipe(loggedIn, map(loggedIn => loggedIn && redirect || true)); diff --git a/src/auth-guard/ng-package.json b/src/auth-guard/ng-package.json new file mode 100644 index 000000000..d3032da35 --- /dev/null +++ b/src/auth-guard/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/auth-guard/package.json b/src/auth-guard/package.json index a559c462f..8dce418a5 100644 --- a/src/auth-guard/package.json +++ b/src/auth-guard/package.json @@ -1,11 +1,3 @@ { - "$schema": "../../node_modules/ng-packagr/package.schema.json", - "ngPackage": { - "lib": { - "entryFile": "public_api.ts", - "umdModuleIds": { - "firebase/app": "firebase" - } - } - } + "$schema": "../../node_modules/ng-packagr/package.schema.json" } diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index c23a99fd6..e20274658 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -1,7 +1,78 @@ -import { NgModule } from '@angular/core'; -import { AngularFireAuth } from './auth'; +import { + EnvironmentProviders, + InjectionToken, + Injector, + NgModule, + NgZone, + Optional, + makeEnvironmentProviders, +} from '@angular/core'; +import { VERSION, ÉĩAngularFireSchedulers, ÉĩgetDefaultInstanceOf } from '@angular/fire'; +import { FirebaseApp, FirebaseApps } from '@angular/fire/app'; +import { AppCheckInstances } from '@angular/fire/app-check'; +import { registerVersion } from 'firebase/app'; +import { Auth as FirebaseAuth } from 'firebase/auth'; +import { AUTH_PROVIDER_NAME, Auth, AuthInstances } from './auth'; + +export const PROVIDED_AUTH_INSTANCES = new InjectionToken('angularfire2.auth-instances'); + +export function defaultAuthInstanceFactory(provided: FirebaseAuth[]|undefined, defaultApp: FirebaseApp) { + const defaultAuth = ÉĩgetDefaultInstanceOf(AUTH_PROVIDER_NAME, provided, defaultApp); + return defaultAuth && new Auth(defaultAuth); +} + +export function authInstanceFactory(fn: (injector: Injector) => FirebaseAuth) { + return (zone: NgZone, injector: Injector) => { + const auth = zone.runOutsideAngular(() => fn(injector)); + return new Auth(auth); + }; +} + +const AUTH_INSTANCES_PROVIDER = { + provide: AuthInstances, + deps: [ + [new Optional(), PROVIDED_AUTH_INSTANCES ], + ] +}; + +const DEFAULT_AUTH_INSTANCE_PROVIDER = { + provide: Auth, + useFactory: defaultAuthInstanceFactory, + deps: [ + [new Optional(), PROVIDED_AUTH_INSTANCES ], + FirebaseApp, + ] +}; @NgModule({ - providers: [ AngularFireAuth ] + providers: [ + DEFAULT_AUTH_INSTANCE_PROVIDER, + AUTH_INSTANCES_PROVIDER, + ] }) -export class AngularFireAuthModule { } +export class AuthModule { + constructor() { + registerVersion('angularfire', VERSION.full, 'auth'); + } +} + +export function provideAuth(fn: (injector: Injector) => FirebaseAuth, ...deps: any[]): EnvironmentProviders { + registerVersion('angularfire', VERSION.full, 'auth'); + return makeEnvironmentProviders([ + DEFAULT_AUTH_INSTANCE_PROVIDER, + AUTH_INSTANCES_PROVIDER, + { + provide: PROVIDED_AUTH_INSTANCES, + useFactory: authInstanceFactory(fn), + multi: true, + deps: [ + NgZone, + Injector, + ÉĩAngularFireSchedulers, + FirebaseApps, + [new Optional(), AppCheckInstances ], + ...deps, + ] + } + ]); +} diff --git a/src/auth/auth.spec.ts b/src/auth/auth.spec.ts index 70aefc9e8..015cd06c9 100644 --- a/src/auth/auth.spec.ts +++ b/src/auth/auth.spec.ts @@ -1,163 +1,39 @@ -import { User } from 'firebase/app'; -import { Observable, Subject } from 'rxjs'; import { TestBed } from '@angular/core/testing'; -import { AngularFireModule, FIREBASE_APP_NAME, FIREBASE_OPTIONS, FirebaseApp } from '@angular/fire'; -import { AngularFireAuth, AngularFireAuthModule } from './public_api'; -import { COMMON_CONFIG } from '../test-config'; -import 'firebase/auth'; -import { rando } from '../firestore/utils.spec'; +import { FirebaseApp, getApp, initializeApp, provideFirebaseApp } from '@angular/fire/app'; +import { Auth, connectAuthEmulator, getAuth, provideAuth } from '@angular/fire/auth'; +import { COMMON_CONFIG, authEmulatorPort } from '../test-config'; +import { rando } from '../utils'; -const firebaseUser = { - uid: '12345', - providerData: [{ displayName: 'jeffbcrossyface' }] -} as User; - -describe('AngularFireAuth', () => { +describe('Auth', () => { let app: FirebaseApp; - let afAuth: AngularFireAuth; - let mockAuthState: Subject; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - AngularFireModule.initializeApp(COMMON_CONFIG, rando()), - AngularFireAuthModule - ] - }); - - app = TestBed.inject(FirebaseApp); - afAuth = TestBed.inject(AngularFireAuth); - - mockAuthState = new Subject(); - // @ts-ignore - spyOn(afAuth, 'authState').and.returnValue(mockAuthState); - // @ts-ignore - spyOn(afAuth, 'idToken').and.returnValue(mockAuthState); - (afAuth as any).authState = mockAuthState as Observable; - (afAuth as any).idToken = mockAuthState as Observable; - }); - - afterEach(() => { - app.delete(); - }); - - describe('Zones', () => { - it('should call operators and subscriber in the same zone as when service was initialized', (done) => { - // Initialize the app outside of the zone, to mimick real life behavior. - const ngZone = Zone.current.fork({ - name: 'ngZone' - }); - ngZone.run(() => { - const subs = [ - afAuth.authState.subscribe(() => { - expect(Zone.current.name).toBe('ngZone'); - done(); - }, done.fail), - afAuth.authState.subscribe(() => { - expect(Zone.current.name).toBe('ngZone'); - done(); - }, done.fail) - ]; - mockAuthState.next(firebaseUser); - subs.forEach(s => s.unsubscribe()); - }); - }); - }); - - it('should exist', () => { - expect(afAuth instanceof AngularFireAuth).toBe(true); - }); - - it('should have an initialized Firebase app', () => { - expect(afAuth.app).toBeDefined(); - }); - - it('should emit auth updates through authState', (done: any) => { - let count = 0; - - // Check that the first value is null and second is the auth user - const subs = afAuth.authState.subscribe({ - next: (user => { - if (count === 0) { - expect(user).toBe(null); - count = count + 1; - mockAuthState.next(firebaseUser); - } else { - expect(user).toEqual(firebaseUser); - subs.unsubscribe(); - done(); - } - }), - error: done, - complete: done.fail + let auth: Auth; + let providedAuth: Auth; + let appName: string; + + describe('single injection', () => { + + beforeEach(() => { + appName = rando(); + TestBed.configureTestingModule({ + providers: [ + provideFirebaseApp(() => initializeApp(COMMON_CONFIG, appName)), + provideAuth(() => { + providedAuth = getAuth(getApp(appName)); + connectAuthEmulator(providedAuth, `http://localhost:${authEmulatorPort}`, { disableWarnings: true }); + return providedAuth; + }), + ], + }); + app = TestBed.inject(FirebaseApp); + auth = TestBed.inject(Auth); + }); + + it('should be injectable', () => { + expect(providedAuth).toBeTruthy(); + expect(auth).toEqual(providedAuth); + expect(auth.app).toEqual(app); }); - mockAuthState.next(null); - }); - it('should emit auth updates through idToken', (done: any) => { - let count = 0; - - // Check that the first value is null and second is the auth user - const subs = afAuth.idToken.subscribe({ - next: user => { - if (count === 0) { - expect(user).toBe(null); - count = count + 1; - mockAuthState.next(firebaseUser); - } else { - expect(user as any).toEqual(firebaseUser); - subs.unsubscribe(); - done(); - } - }, - error: done, - complete: done.fail - }); - mockAuthState.next(null); - }); - -}); - -const FIREBASE_APP_NAME_TOO = (Math.random() + 1).toString(36).substring(7); - -describe('AngularFireAuth with different app', () => { - let app: FirebaseApp; - let afAuth: AngularFireAuth; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - AngularFireModule.initializeApp(COMMON_CONFIG, rando()), - AngularFireAuthModule - ], - providers: [ - { provide: FIREBASE_APP_NAME, useValue: FIREBASE_APP_NAME_TOO }, - { provide: FIREBASE_OPTIONS, useValue: COMMON_CONFIG } - ] - }); - - app = TestBed.inject(FirebaseApp); - afAuth = TestBed.inject(AngularFireAuth); - }); - - afterEach(() => { - app.delete(); - }); - - describe('', () => { - - it('should be an AngularFireAuth type', () => { - expect(afAuth instanceof AngularFireAuth).toEqual(true); - }); - - it('should have an initialized Firebase app', () => { - expect(afAuth.app).toBeDefined(); - }); - - it('should have an initialized Firebase app instance member', async () => { - const app = await afAuth.app; - expect(app.name).toEqual(FIREBASE_APP_NAME_TOO); - }); }); }); diff --git a/src/auth/auth.ts b/src/auth/auth.ts index 5f6dabd2e..39a4ae848 100644 --- a/src/auth/auth.ts +++ b/src/auth/auth.ts @@ -1,106 +1,30 @@ -import { Injectable, Inject, Optional, NgZone, PLATFORM_ID } from '@angular/core'; -import { Observable, of, from } from 'rxjs'; -import { switchMap, map, observeOn, shareReplay, first, tap } from 'rxjs/operators'; -import { - FIREBASE_OPTIONS, - FIREBASE_APP_NAME, - FirebaseOptions, - FirebaseAppConfig, - ÉĩPromiseProxy, - ÉĩlazySDKProxy, - ÉĩfirebaseAppFactory, - ÉĩAngularFireSchedulers, - ÉĩkeepUnstableUntilFirstFactory -} from '@angular/fire'; -import { User, auth } from 'firebase/app'; -import { isPlatformServer } from '@angular/common'; -import firebase from 'firebase/app'; +import { ÉĩgetAllInstancesOf } from '@angular/fire'; +import { Auth as FirebaseAuth } from 'firebase/auth'; +import { from, timer } from 'rxjs'; +import { concatMap, distinct } from 'rxjs/operators'; -export interface AngularFireAuth extends ÉĩPromiseProxy {} +export const AUTH_PROVIDER_NAME = 'auth'; -@Injectable({ - providedIn: 'any' -}) -export class AngularFireAuth { +// see notes in core/firebase.app.module.ts for why we're building the class like this + +export interface Auth extends FirebaseAuth {} - /** - * Observable of authentication state; as of Firebase 4.0 this is only triggered via sign-in/out - */ - public readonly authState: Observable; - - /** - * Observable of the currently signed-in user's JWT token used to identify the user to a Firebase service (or null). - */ - public readonly idToken: Observable; - - /** - * Observable of the currently signed-in user (or null). - */ - public readonly user: Observable; - - /** - * Observable of the currently signed-in user's IdTokenResult object which contains the ID token JWT string and other - * helper properties for getting different data associated with the token as well as all the decoded payload claims - * (or null). - */ - public readonly idTokenResult: Observable; - - constructor( - @Inject(FIREBASE_OPTIONS) options: FirebaseOptions, - @Optional() @Inject(FIREBASE_APP_NAME) nameOrConfig: string|FirebaseAppConfig|null|undefined, - // tslint:disable-next-line:ban-types - @Inject(PLATFORM_ID) platformId: Object, - zone: NgZone - ) { - const schedulers = new ÉĩAngularFireSchedulers(zone); - const keepUnstableUntilFirst = ÉĩkeepUnstableUntilFirstFactory(schedulers); - - const auth = of(undefined).pipe( - observeOn(schedulers.outsideAngular), - switchMap(() => zone.runOutsideAngular(() => import('firebase/auth'))), - tap((it: any) => it), // It seems I need to touch the import for it to do anything... race maybe? - map(() => ÉĩfirebaseAppFactory(options, zone, nameOrConfig)), - map(app => zone.runOutsideAngular(() => app.auth())), - shareReplay({ bufferSize: 1, refCount: false }), - ); - - if (isPlatformServer(platformId)) { - - this.authState = this.user = this.idToken = this.idTokenResult = of(null); - - } else { - - // HACK, as we're exporting auth.Auth, rather than auth, developers importing firebase.auth - // (e.g, `import { auth } from 'firebase/app'`) are getting an undefined auth object unexpectedly - // as we're completely lazy. Let's eagerly load the Auth SDK here. - // There could potentially be race conditions still... but this greatly decreases the odds while - // we reevaluate the API. - const _ = auth.pipe(first()).subscribe(); - - this.authState = auth.pipe( - switchMap(auth => auth.getRedirectResult().then(() => auth)), - switchMap(auth => zone.runOutsideAngular(() => new Observable(auth.onAuthStateChanged.bind(auth)))), - keepUnstableUntilFirst - ); - - this.user = auth.pipe( - switchMap(auth => auth.getRedirectResult().then(() => auth)), - switchMap(auth => zone.runOutsideAngular(() => new Observable(auth.onIdTokenChanged.bind(auth)))), - keepUnstableUntilFirst - ); - - this.idToken = this.user.pipe( - switchMap(user => user ? from(user.getIdToken()) : of(null)) - ); - - this.idTokenResult = this.user.pipe( - switchMap(user => user ? from(user.getIdTokenResult()) : of(null)) - ); - - } +export class Auth { + constructor(auth: FirebaseAuth) { + return auth; + } +} - return ÉĩlazySDKProxy(this, auth, zone); + +export interface AuthInstances extends Array {} +export class AuthInstances { + constructor() { + return ÉĩgetAllInstancesOf(AUTH_PROVIDER_NAME); } - } + +export const authInstance$ = timer(0, 300).pipe( + concatMap(() => from(ÉĩgetAllInstancesOf(AUTH_PROVIDER_NAME))), + distinct(), +); diff --git a/src/auth/firebase.ts b/src/auth/firebase.ts new file mode 100644 index 000000000..743ca90d8 --- /dev/null +++ b/src/auth/firebase.ts @@ -0,0 +1,112 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +export * from 'firebase/auth'; +import { ÉĩzoneWrap } from '@angular/fire'; +import { + applyActionCode as _applyActionCode, + beforeAuthStateChanged as _beforeAuthStateChanged, + checkActionCode as _checkActionCode, + confirmPasswordReset as _confirmPasswordReset, + connectAuthEmulator as _connectAuthEmulator, + createUserWithEmailAndPassword as _createUserWithEmailAndPassword, + deleteUser as _deleteUser, + fetchSignInMethodsForEmail as _fetchSignInMethodsForEmail, + getAdditionalUserInfo as _getAdditionalUserInfo, + getAuth as _getAuth, + getIdToken as _getIdToken, + getIdTokenResult as _getIdTokenResult, + getMultiFactorResolver as _getMultiFactorResolver, + getRedirectResult as _getRedirectResult, + initializeAuth as _initializeAuth, + initializeRecaptchaConfig as _initializeRecaptchaConfig, + isSignInWithEmailLink as _isSignInWithEmailLink, + linkWithCredential as _linkWithCredential, + linkWithPhoneNumber as _linkWithPhoneNumber, + linkWithPopup as _linkWithPopup, + linkWithRedirect as _linkWithRedirect, + onAuthStateChanged as _onAuthStateChanged, + onIdTokenChanged as _onIdTokenChanged, + parseActionCodeURL as _parseActionCodeURL, + reauthenticateWithCredential as _reauthenticateWithCredential, + reauthenticateWithPhoneNumber as _reauthenticateWithPhoneNumber, + reauthenticateWithPopup as _reauthenticateWithPopup, + reauthenticateWithRedirect as _reauthenticateWithRedirect, + reload as _reload, + revokeAccessToken as _revokeAccessToken, + sendEmailVerification as _sendEmailVerification, + sendPasswordResetEmail as _sendPasswordResetEmail, + sendSignInLinkToEmail as _sendSignInLinkToEmail, + setPersistence as _setPersistence, + signInAnonymously as _signInAnonymously, + signInWithCredential as _signInWithCredential, + signInWithCustomToken as _signInWithCustomToken, + signInWithEmailAndPassword as _signInWithEmailAndPassword, + signInWithEmailLink as _signInWithEmailLink, + signInWithPhoneNumber as _signInWithPhoneNumber, + signInWithPopup as _signInWithPopup, + signInWithRedirect as _signInWithRedirect, + signOut as _signOut, + unlink as _unlink, + updateCurrentUser as _updateCurrentUser, + updateEmail as _updateEmail, + updatePassword as _updatePassword, + updatePhoneNumber as _updatePhoneNumber, + updateProfile as _updateProfile, + useDeviceLanguage as _useDeviceLanguage, + validatePassword as _validatePassword, + verifyBeforeUpdateEmail as _verifyBeforeUpdateEmail, + verifyPasswordResetCode as _verifyPasswordResetCode +} from 'firebase/auth'; + +export const applyActionCode = ÉĩzoneWrap(_applyActionCode, true); +export const beforeAuthStateChanged = ÉĩzoneWrap(_beforeAuthStateChanged, true); +export const checkActionCode = ÉĩzoneWrap(_checkActionCode, true); +export const confirmPasswordReset = ÉĩzoneWrap(_confirmPasswordReset, true, 2); +export const connectAuthEmulator = ÉĩzoneWrap(_connectAuthEmulator, true); +export const createUserWithEmailAndPassword = ÉĩzoneWrap(_createUserWithEmailAndPassword, true, 2); +export const deleteUser = ÉĩzoneWrap(_deleteUser, true, 2); +export const fetchSignInMethodsForEmail = ÉĩzoneWrap(_fetchSignInMethodsForEmail, true, 2); +export const getAdditionalUserInfo = ÉĩzoneWrap(_getAdditionalUserInfo, true, 2); +export const getAuth = ÉĩzoneWrap(_getAuth, true); +export const getIdToken = ÉĩzoneWrap(_getIdToken, true); +export const getIdTokenResult = ÉĩzoneWrap(_getIdTokenResult, true); +export const getMultiFactorResolver = ÉĩzoneWrap(_getMultiFactorResolver, true); +export const getRedirectResult = ÉĩzoneWrap(_getRedirectResult, true); +export const initializeAuth = ÉĩzoneWrap(_initializeAuth, true); +export const initializeRecaptchaConfig = ÉĩzoneWrap(_initializeRecaptchaConfig, true); +export const isSignInWithEmailLink = ÉĩzoneWrap(_isSignInWithEmailLink, true); +export const linkWithCredential = ÉĩzoneWrap(_linkWithCredential, true, 2); +export const linkWithPhoneNumber = ÉĩzoneWrap(_linkWithPhoneNumber, true, 2); +export const linkWithPopup = ÉĩzoneWrap(_linkWithPopup, true, 2); +export const linkWithRedirect = ÉĩzoneWrap(_linkWithRedirect, true, 2); +export const onAuthStateChanged = ÉĩzoneWrap(_onAuthStateChanged, true); +export const onIdTokenChanged = ÉĩzoneWrap(_onIdTokenChanged, true); +export const parseActionCodeURL = ÉĩzoneWrap(_parseActionCodeURL, true); +export const reauthenticateWithCredential = ÉĩzoneWrap(_reauthenticateWithCredential, true, 2); +export const reauthenticateWithPhoneNumber = ÉĩzoneWrap(_reauthenticateWithPhoneNumber, true, 2); +export const reauthenticateWithPopup = ÉĩzoneWrap(_reauthenticateWithPopup, true, 2); +export const reauthenticateWithRedirect = ÉĩzoneWrap(_reauthenticateWithRedirect, true, 2); +export const reload = ÉĩzoneWrap(_reload, true, 2); +export const revokeAccessToken = ÉĩzoneWrap(_revokeAccessToken, true, 2); +export const sendEmailVerification = ÉĩzoneWrap(_sendEmailVerification, true, 2); +export const sendPasswordResetEmail = ÉĩzoneWrap(_sendPasswordResetEmail, true, 2); +export const sendSignInLinkToEmail = ÉĩzoneWrap(_sendSignInLinkToEmail, true, 2); +export const setPersistence = ÉĩzoneWrap(_setPersistence, true); +export const signInAnonymously = ÉĩzoneWrap(_signInAnonymously, true, 2); +export const signInWithCredential = ÉĩzoneWrap(_signInWithCredential, true, 2); +export const signInWithCustomToken = ÉĩzoneWrap(_signInWithCustomToken, true, 2); +export const signInWithEmailAndPassword = ÉĩzoneWrap(_signInWithEmailAndPassword, true, 2); +export const signInWithEmailLink = ÉĩzoneWrap(_signInWithEmailLink, true, 2); +export const signInWithPhoneNumber = ÉĩzoneWrap(_signInWithPhoneNumber, true, 2); +export const signInWithPopup = ÉĩzoneWrap(_signInWithPopup, true, 2); +export const signInWithRedirect = ÉĩzoneWrap(_signInWithRedirect, true, 2); +export const signOut = ÉĩzoneWrap(_signOut, true, 2); +export const unlink = ÉĩzoneWrap(_unlink, true, 2); +export const updateCurrentUser = ÉĩzoneWrap(_updateCurrentUser, true, 2); +export const updateEmail = ÉĩzoneWrap(_updateEmail, true, 2); +export const updatePassword = ÉĩzoneWrap(_updatePassword, true, 2); +export const updatePhoneNumber = ÉĩzoneWrap(_updatePhoneNumber, true, 2); +export const updateProfile = ÉĩzoneWrap(_updateProfile, true, 2); +export const useDeviceLanguage = ÉĩzoneWrap(_useDeviceLanguage, true, 2); +export const validatePassword = ÉĩzoneWrap(_validatePassword, true, 2); +export const verifyBeforeUpdateEmail = ÉĩzoneWrap(_verifyBeforeUpdateEmail, true, 2); +export const verifyPasswordResetCode = ÉĩzoneWrap(_verifyPasswordResetCode, true, 2); diff --git a/src/auth/ng-package.json b/src/auth/ng-package.json new file mode 100644 index 000000000..d3032da35 --- /dev/null +++ b/src/auth/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/auth/package.json b/src/auth/package.json index a559c462f..5e8aa1496 100644 --- a/src/auth/package.json +++ b/src/auth/package.json @@ -1,11 +1,3 @@ { - "$schema": "../../node_modules/ng-packagr/package.schema.json", - "ngPackage": { - "lib": { - "entryFile": "public_api.ts", - "umdModuleIds": { - "firebase/app": "firebase" - } - } - } -} + "$schema": "../../node_modules/ng-packagr/package.schema.json" +} \ No newline at end of file diff --git a/src/auth/public_api.ts b/src/auth/public_api.ts index f570236a4..6267eb92a 100644 --- a/src/auth/public_api.ts +++ b/src/auth/public_api.ts @@ -1,2 +1,4 @@ -export * from './auth'; -export * from './auth.module'; +export { Auth, AuthInstances, authInstance$ } from './auth'; +export { provideAuth, AuthModule } from './auth.module'; +export * from './rxfire'; +export * from './firebase'; diff --git a/src/auth/rxfire.ts b/src/auth/rxfire.ts new file mode 100644 index 000000000..561f853bb --- /dev/null +++ b/src/auth/rxfire.ts @@ -0,0 +1,11 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +import { ÉĩzoneWrap } from '@angular/fire'; +import { + authState as _authState, + idToken as _idToken, + user as _user +} from 'rxfire/auth'; + +export const authState = ÉĩzoneWrap(_authState, true); +export const idToken = ÉĩzoneWrap(_idToken, true); +export const user = ÉĩzoneWrap(_user, true); diff --git a/src/compat/analytics/analytics.module.ts b/src/compat/analytics/analytics.module.ts new file mode 100644 index 000000000..dd9c0d6a1 --- /dev/null +++ b/src/compat/analytics/analytics.module.ts @@ -0,0 +1,23 @@ +import { NgModule, Optional } from '@angular/core'; +import { VERSION } from '@angular/fire'; +import firebase from 'firebase/compat/app'; +import { AngularFireAnalytics } from './analytics'; +import { ScreenTrackingService } from './screen-tracking.service'; +import { UserTrackingService } from './user-tracking.service'; + +@NgModule({ + providers: [ AngularFireAnalytics ] +}) +export class AngularFireAnalyticsModule { + constructor( + analytics: AngularFireAnalytics, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + @Optional() screenTracking: ScreenTrackingService, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + @Optional() userTracking: UserTrackingService, + ) { + firebase.registerVersion('angularfire', VERSION.full, 'analytics-compat'); + // calling anything on analytics will eagerly load the SDK + analytics.app.then(() => undefined); + } +} diff --git a/src/compat/analytics/analytics.spec.ts b/src/compat/analytics/analytics.spec.ts new file mode 100644 index 000000000..a05a23c5f --- /dev/null +++ b/src/compat/analytics/analytics.spec.ts @@ -0,0 +1,30 @@ +import { TestBed } from '@angular/core/testing'; +import { AngularFireModule } from '@angular/fire/compat'; +import { AngularFireAnalytics, AngularFireAnalyticsModule } from '@angular/fire/compat/analytics'; +import { COMMON_CONFIG } from '../../test-config'; +import { rando } from '../../utils'; + + +describe('AngularFireAnalytics', () => { + let analytics: AngularFireAnalytics; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + AngularFireModule.initializeApp(COMMON_CONFIG, rando()), + AngularFireAnalyticsModule + ] + }); + + analytics = TestBed.inject(AngularFireAnalytics); + }); + + it('should be exist', () => { + expect(analytics instanceof AngularFireAnalytics).toBe(true); + }); + + it('should have the Firebase Functions instance', () => { + expect(analytics.app).toBeDefined(); + }); + +}); diff --git a/src/compat/analytics/analytics.ts b/src/compat/analytics/analytics.ts new file mode 100644 index 000000000..b932a04fc --- /dev/null +++ b/src/compat/analytics/analytics.ts @@ -0,0 +1,162 @@ +import { isPlatformBrowser } from '@angular/common'; +import { Inject, Injectable, InjectionToken, NgZone, Optional, PLATFORM_ID } from '@angular/core'; +import { ÉĩAngularFireSchedulers } from '@angular/fire'; +import { ÉĩPromiseProxy, ÉĩapplyMixins, ÉĩcacheInstance, ÉĩlazySDKProxy } from '@angular/fire/compat'; +import { FirebaseApp } from '@angular/fire/compat'; +import { isSupported } from 'firebase/analytics'; +import firebase from 'firebase/compat/app'; +import { EMPTY, of } from 'rxjs'; +import { map, observeOn, shareReplay, switchMap } from 'rxjs/operators'; +import { proxyPolyfillCompat } from './base'; + +export type Config = Record; + +export const COLLECTION_ENABLED = new InjectionToken('angularfire2.analytics.analyticsCollectionEnabled'); +export const APP_VERSION = new InjectionToken('angularfire2.analytics.appVersion'); +export const APP_NAME = new InjectionToken('angularfire2.analytics.appName'); +export const DEBUG_MODE = new InjectionToken('angularfire2.analytics.debugMode'); +export const CONFIG = new InjectionToken('angularfire2.analytics.config'); + +const APP_NAME_KEY = 'app_name'; +const APP_VERSION_KEY = 'app_version'; +const DEBUG_MODE_KEY = 'debug_mode'; +const GTAG_CONFIG_COMMAND = 'config'; +const GTAG_FUNCTION_NAME = 'gtag'; // TODO rename these +const DATA_LAYER_NAME = 'dataLayer'; +const SEND_TO_KEY = 'send_to'; + + +export interface AngularFireAnalytics extends ÉĩPromiseProxy { +} + +@Injectable({ + providedIn: 'any' +}) +export class AngularFireAnalytics { + + private measurementId: string; + private analyticsInitialized = new Promise(() => undefined); + + async updateConfig(config: Config) { + await this.analyticsInitialized; + window[GTAG_FUNCTION_NAME](GTAG_CONFIG_COMMAND, this.measurementId, { ...config, update: true }); + } + + constructor( + app: FirebaseApp, + @Optional() @Inject(COLLECTION_ENABLED) analyticsCollectionEnabled: boolean | null, + @Optional() @Inject(APP_VERSION) providedAppVersion: string | null, + @Optional() @Inject(APP_NAME) providedAppName: string | null, + @Optional() @Inject(DEBUG_MODE) debugModeEnabled: boolean | null, + @Optional() @Inject(CONFIG) providedConfig: Config | null, + + @Inject(PLATFORM_ID) platformId: object, + zone: NgZone, + schedulers: ÉĩAngularFireSchedulers, + ) { + + if (isPlatformBrowser(platformId)) { + + window[DATA_LAYER_NAME] = window[DATA_LAYER_NAME] || []; + + // It turns out we can't rely on the measurementId in the Firebase config JSON + // this identifier is not stable. firebase/analytics does a call to get a fresh value + // falling back on the one in the config. Rather than do that ourselves we should listen + // on our gtag function for a analytics config command + // e.g, ['config', measurementId, { origin: 'firebase', firebase_id }] + const parseMeasurementId = (...args: any[]) => { + if (args[0] === 'config' && args[2].origin === 'firebase') { + this.measurementId = args[1]; + return true; + } else { + return false; + } + }; + + const patchGtag = (fn?: (...args: any[]) => void) => { + window[GTAG_FUNCTION_NAME] = (...args: any[]) => { + if (fn) { + fn(...args); + } + // Inject app_name and app_version into events + // TODO(jamesdaniels): I'm doing this as documented but it's still not + // showing up in the console. Investigate. Guessing it's just part of the + // whole GA4 transition mess. + if (args[0] === 'event' && args[2][SEND_TO_KEY] === this.measurementId) { + if (providedAppName) { + args[2][APP_NAME_KEY] = providedAppName; + } + if (providedAppVersion) { + args[2][APP_VERSION_KEY] = providedAppVersion; + } + } + if (debugModeEnabled && typeof console !== 'undefined') { + // eslint-disable-next-line no-console + console.info(...args); + } + /** + * According to the gtag documentation, this function that defines a custom data layer cannot be + * an arrow function because 'arguments' is not an array. It is actually an object that behaves + * like an array and contains more information then just indexes. Transforming this into arrow function + * caused issue #2505 where analytics no longer sent any data. + */ + (function(..._args: any[]) { + window[DATA_LAYER_NAME].push(arguments); + })(...args); + }; + }; + + // Unclear if we still need to but I was running into config/events I passed + // to gtag before ['js' timestamp] weren't getting parsed, so let's make a promise + // that resolves when firebase/analytics has configured gtag.js that we wait on + // before sending anything + const firebaseAnalyticsAlreadyInitialized = window[DATA_LAYER_NAME].some(parseMeasurementId); + if (firebaseAnalyticsAlreadyInitialized) { + this.analyticsInitialized = Promise.resolve(); + patchGtag(); + } else { + this.analyticsInitialized = new Promise(resolve => { + patchGtag((...args) => { + if (parseMeasurementId(...args)) { + resolve(); + } + }); + }); + } + + if (providedConfig) { + this.updateConfig(providedConfig); + } + if (debugModeEnabled) { + this.updateConfig({ [DEBUG_MODE_KEY]: 1 }); + } + + } else { + + this.analyticsInitialized = Promise.resolve(); + + } + + const analytics = of(undefined).pipe( + observeOn(schedulers.outsideAngular), + switchMap(isSupported), + switchMap(supported => supported ? zone.runOutsideAngular(() => import('firebase/compat/analytics')) : EMPTY), + map(() => { + return ÉĩcacheInstance(`analytics`, 'AngularFireAnalytics', app.name, () => { + const analytics = app.analytics(); + if (analyticsCollectionEnabled === false) { + analytics.setAnalyticsCollectionEnabled(false); + } + return analytics; + }, [app, analyticsCollectionEnabled, providedConfig, debugModeEnabled]); + }), + shareReplay({ bufferSize: 1, refCount: false }) + ); + + return ÉĩlazySDKProxy(this, analytics, zone); + + } + +} + +ÉĩapplyMixins(AngularFireAnalytics, [proxyPolyfillCompat]); diff --git a/src/compat/analytics/base.ts b/src/compat/analytics/base.ts new file mode 100644 index 000000000..2af7d5d77 --- /dev/null +++ b/src/compat/analytics/base.ts @@ -0,0 +1,10 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +// Export a null object with the same keys as firebase/compat/analytics, so Proxy can work with proxy-polyfill in Internet Explorer +export const proxyPolyfillCompat = { + app: null, + logEvent: null, + setCurrentScreen: null, + setUserId: null, + setUserProperties: null, + setAnalyticsCollectionEnabled: null, +}; diff --git a/src/analytics/index.ts b/src/compat/analytics/index.ts similarity index 100% rename from src/analytics/index.ts rename to src/compat/analytics/index.ts diff --git a/src/compat/analytics/ng-package.json b/src/compat/analytics/ng-package.json new file mode 100644 index 000000000..e5e5ccdfc --- /dev/null +++ b/src/compat/analytics/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/compat/analytics/package.json b/src/compat/analytics/package.json new file mode 100644 index 000000000..5eaf9d5f0 --- /dev/null +++ b/src/compat/analytics/package.json @@ -0,0 +1,3 @@ +{ + "$schema": "../../../node_modules/ng-packagr/package.schema.json" +} diff --git a/src/compat/analytics/public_api.ts b/src/compat/analytics/public_api.ts new file mode 100644 index 000000000..4601131aa --- /dev/null +++ b/src/compat/analytics/public_api.ts @@ -0,0 +1,4 @@ +export * from './analytics'; +export * from './analytics.module'; +export * from './screen-tracking.service'; +export * from './user-tracking.service'; diff --git a/src/compat/analytics/screen-tracking.service.ts b/src/compat/analytics/screen-tracking.service.ts new file mode 100644 index 000000000..5ceec926b --- /dev/null +++ b/src/compat/analytics/screen-tracking.service.ts @@ -0,0 +1,47 @@ +import { ComponentFactoryResolver, Injectable, NgZone, OnDestroy, Optional } from '@angular/core'; +import { VERSION } from '@angular/fire'; +import { ÉĩscreenViewEvent } from '@angular/fire/analytics'; +import { Title } from '@angular/platform-browser'; +import { Router } from '@angular/router'; +import firebase from 'firebase/compat/app'; +import { Subscription } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; +import { AngularFireAnalytics } from './analytics'; +import { UserTrackingService } from './user-tracking.service'; + +const SCREEN_VIEW_EVENT = 'screen_view'; + +@Injectable() +export class ScreenTrackingService implements OnDestroy { + + private disposable: Subscription | undefined; + + constructor( + analytics: AngularFireAnalytics, + @Optional() router: Router, + @Optional() title: Title, + componentFactoryResolver: ComponentFactoryResolver, + zone: NgZone, + @Optional() userTrackingService: UserTrackingService, + ) { + firebase.registerVersion('angularfire', VERSION.full, 'compat-screen-tracking'); + if (!router || !analytics) { return this; } + zone.runOutsideAngular(() => { + this.disposable = ÉĩscreenViewEvent(router, title, componentFactoryResolver).pipe( + switchMap(async params => { + if (userTrackingService) { + await userTrackingService.initialized; + } + return await analytics.logEvent(SCREEN_VIEW_EVENT, params); + }) + ).subscribe(); + }); + } + + ngOnDestroy() { + if (this.disposable) { + this.disposable.unsubscribe(); + } + } + +} diff --git a/src/compat/analytics/user-tracking.service.ts b/src/compat/analytics/user-tracking.service.ts new file mode 100644 index 000000000..480f8384a --- /dev/null +++ b/src/compat/analytics/user-tracking.service.ts @@ -0,0 +1,51 @@ +import { isPlatformBrowser } from '@angular/common'; +import { Inject, Injectable, NgZone, OnDestroy, PLATFORM_ID } from '@angular/core'; +import { VERSION } from '@angular/fire'; +import { AngularFireAuth } from '@angular/fire/compat/auth'; +import firebase from 'firebase/compat/app'; +import { Subscription } from 'rxjs'; +import { AngularFireAnalytics } from './analytics'; + +@Injectable() +export class UserTrackingService implements OnDestroy { + + initialized: Promise; + private disposables: Subscription[] = []; + + // TODO a user properties injector + constructor( + analytics: AngularFireAnalytics, + + @Inject(PLATFORM_ID) platformId: object, + auth: AngularFireAuth, + zone: NgZone, + ) { + firebase.registerVersion('angularfire', VERSION.full, 'compat-user-tracking'); + if (isPlatformBrowser(platformId)) { + let resolveInitialized; + this.initialized = zone.runOutsideAngular(() => new Promise(resolve => resolveInitialized = resolve)); + this.disposables = [ + auth.authState.subscribe(user => { + analytics.setUserId(user?.uid); + resolveInitialized(); + }), + auth.credential.subscribe(credential => { + if (credential) { + const method = credential.user.isAnonymous ? 'anonymous' : credential.additionalUserInfo.providerId; + if (credential.additionalUserInfo.isNewUser) { + analytics.logEvent('sign_up', { method }); + } + analytics.logEvent('login', { method }); + } + }) + ]; + } else { + this.initialized = Promise.resolve(); + } + + } + + ngOnDestroy() { + this.disposables.forEach(it => it.unsubscribe()); + } +} diff --git a/src/compat/angularfire2.spec.ts b/src/compat/angularfire2.spec.ts new file mode 100644 index 000000000..dc33f1722 --- /dev/null +++ b/src/compat/angularfire2.spec.ts @@ -0,0 +1,94 @@ +import { TestBed } from '@angular/core/testing'; +import { ÉĩZoneScheduler } from '@angular/fire'; +import { AngularFireModule, FirebaseApp } from '@angular/fire/compat'; +import { TestScheduler } from 'rxjs/testing'; +import { COMMON_CONFIG } from '../../src/test-config'; +import { rando } from '../../src/utils'; + +describe('angularfire', () => { + let app: FirebaseApp; + let appName: string; + + beforeEach(() => { + + appName = rando(); + + TestBed.configureTestingModule({ + imports: [AngularFireModule.initializeApp(COMMON_CONFIG, appName)] + }); + + app = TestBed.inject(FirebaseApp); + }); + + describe('ZoneScheduler', () => { + it('should execute the scheduled work inside the specified zone', done => { + const ngZone = Zone.current.fork({ + name: 'ngZone' + }); + const rootZone = Zone.current; + + // Mimic real behavior: Executing in Angular + ngZone.run(() => { + const outsideAngularScheduler = new ÉĩZoneScheduler(rootZone); + outsideAngularScheduler.schedule(() => { + expect(Zone.current.name).not.toEqual('ngZone'); + done(); + }); + }); + }); + + it('should execute nested scheduled work inside the specified zone', done => { + const testScheduler = new TestScheduler(null); + testScheduler.run(helpers => { + const outsideAngularScheduler = new ÉĩZoneScheduler(Zone.current, testScheduler); + + const ngZone = Zone.current.fork({ + name: 'ngZone' + }); + + let callbacksRan = 0; + + // Mimic real behavior: Executing in Angular + ngZone.run(() => { + outsideAngularScheduler.schedule(() => { + callbacksRan++; + expect(Zone.current.name).not.toEqual('ngZone'); + + ngZone.run(() => { + // Sync queueing + outsideAngularScheduler.schedule(() => { + callbacksRan++; + expect(Zone.current.name).not.toEqual('ngZone'); + }); + + // Async (10ms delay) nested scheduling + outsideAngularScheduler.schedule(() => { + callbacksRan++; + expect(Zone.current.name).not.toEqual('ngZone'); + }, 10); + + // Simulate flush from inside angular- + helpers.flush(); + done(); + expect(callbacksRan).toEqual(3); + }); + }); + helpers.flush(); + }); + }); + }); + }); + + describe('FirebaseApp', () => { + + it('should provide a FirebaseApp for the FirebaseApp binding', () => { + expect(typeof app.delete).toBe('function'); + }); + + if (typeof window !== 'undefined') { + it('should have the provided name', () => { + expect(app.name).toBe(appName); + }); + } + }); +}); diff --git a/src/compat/auth-guard/auth-guard.module.ts b/src/compat/auth-guard/auth-guard.module.ts new file mode 100644 index 000000000..42012c6f9 --- /dev/null +++ b/src/compat/auth-guard/auth-guard.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { VERSION } from '@angular/fire'; +import firebase from 'firebase/compat/app'; +import { AngularFireAuthGuard } from './auth-guard'; + +@NgModule({ + providers: [ AngularFireAuthGuard ] +}) +export class AngularFireAuthGuardModule { + constructor() { + firebase.registerVersion('angularfire', VERSION.full, 'auth-guard-compat'); + } +} diff --git a/src/compat/auth-guard/auth-guard.spec.ts b/src/compat/auth-guard/auth-guard.spec.ts new file mode 100644 index 000000000..808417461 --- /dev/null +++ b/src/compat/auth-guard/auth-guard.spec.ts @@ -0,0 +1,34 @@ +import { APP_BASE_HREF } from '@angular/common'; +import { TestBed } from '@angular/core/testing'; +import { AngularFireModule } from '@angular/fire/compat'; +import { AngularFireAuthGuard, AngularFireAuthGuardModule } from '@angular/fire/compat/auth-guard'; +import { Router, RouterModule } from '@angular/router'; +import { COMMON_CONFIG } from '../../../src/test-config'; +import { rando } from '../../../src/utils'; + +class TestComponent { } + +describe('AngularFireAuthGuard', () => { + let router: Router; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + AngularFireModule.initializeApp(COMMON_CONFIG, rando()), + AngularFireAuthGuardModule, + RouterModule.forRoot([ + { path: 'a', component: TestComponent, canActivate: [AngularFireAuthGuard] } + ]) + ], + providers: [ + { provide: APP_BASE_HREF, useValue: 'http://localhost:4200/' } + ] + }); + + router = TestBed.inject(Router); + }); + + it('should be injectable', () => { + expect(router).toBeTruthy(); + }); +}); diff --git a/src/compat/auth-guard/auth-guard.ts b/src/compat/auth-guard/auth-guard.ts new file mode 100644 index 000000000..9be909838 --- /dev/null +++ b/src/compat/auth-guard/auth-guard.ts @@ -0,0 +1,55 @@ +import { Injectable } from '@angular/core'; +import { AngularFireAuth } from '@angular/fire/compat/auth'; +import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; +import firebase from 'firebase/compat/app'; +import { Observable, UnaryFunction, of, pipe } from 'rxjs'; +import { map, switchMap, take } from 'rxjs/operators'; + +export type AuthPipeGenerator = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot) => AuthPipe; +export type AuthPipe = UnaryFunction, Observable>; + +export const loggedIn: AuthPipe = map(user => !!user); + +@Injectable({ + providedIn: 'any' +}) +export class AngularFireAuthGuard implements CanActivate { + + constructor(private router: Router, private auth: AngularFireAuth) {} + + canActivate = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot) => { + const authPipeFactory = next.data.authGuardPipe as AuthPipeGenerator || (() => loggedIn); + return this.auth.user.pipe( + take(1), + authPipeFactory(next, state), + map(can => { + if (typeof can === 'boolean') { + return can; + } else if (Array.isArray(can)) { + return this.router.createUrlTree(can); + } else { + // TODO(EdricChan03): Add tests + return this.router.parseUrl(can); + } + }) + ); + } + +} + +export const canActivate = (pipe: AuthPipeGenerator) => ({ + canActivate: [ AngularFireAuthGuard ], data: { authGuardPipe: pipe } +}); + + +export const isNotAnonymous: AuthPipe = map(user => !!user && !user.isAnonymous); +export const idTokenResult = switchMap((user: firebase.User|null) => user ? user.getIdTokenResult() : of(null)); +export const emailVerified: AuthPipe = map(user => !!user && user.emailVerified); +export const customClaims = pipe(idTokenResult, map(idTokenResult => idTokenResult ? idTokenResult.claims : [])); +export const hasCustomClaim: (claim: string) => AuthPipe = + // eslint-disable-next-line no-prototype-builtins + (claim) => pipe(customClaims, map(claims => claims.hasOwnProperty(claim))); +export const redirectUnauthorizedTo: (redirect: string|any[]) => AuthPipe = + (redirect) => pipe(loggedIn, map(loggedIn => loggedIn || redirect)); +export const redirectLoggedInTo: (redirect: string|any[]) => AuthPipe = + (redirect) => pipe(loggedIn, map(loggedIn => loggedIn && redirect || true)); diff --git a/src/compat/auth-guard/ng-package.json b/src/compat/auth-guard/ng-package.json new file mode 100644 index 000000000..e5e5ccdfc --- /dev/null +++ b/src/compat/auth-guard/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/compat/auth-guard/package.json b/src/compat/auth-guard/package.json new file mode 100644 index 000000000..5eaf9d5f0 --- /dev/null +++ b/src/compat/auth-guard/package.json @@ -0,0 +1,3 @@ +{ + "$schema": "../../../node_modules/ng-packagr/package.schema.json" +} diff --git a/src/compat/auth-guard/public_api.ts b/src/compat/auth-guard/public_api.ts new file mode 100644 index 000000000..468fd9657 --- /dev/null +++ b/src/compat/auth-guard/public_api.ts @@ -0,0 +1,2 @@ +export * from './auth-guard'; +export * from './auth-guard.module'; diff --git a/src/compat/auth/auth.module.ts b/src/compat/auth/auth.module.ts new file mode 100644 index 000000000..3cc268eef --- /dev/null +++ b/src/compat/auth/auth.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { VERSION } from '@angular/fire'; +import firebase from 'firebase/compat/app'; +import { AngularFireAuth } from './auth'; + +@NgModule({ + providers: [ AngularFireAuth ] +}) +export class AngularFireAuthModule { + constructor() { + firebase.registerVersion('angularfire', VERSION.full, 'auth-compat'); + } +} diff --git a/src/compat/auth/auth.spec.ts b/src/compat/auth/auth.spec.ts new file mode 100644 index 000000000..3cc191666 --- /dev/null +++ b/src/compat/auth/auth.spec.ts @@ -0,0 +1,164 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import { TestBed } from '@angular/core/testing'; +import { AngularFireModule, FIREBASE_APP_NAME, FIREBASE_OPTIONS, FirebaseApp } from '@angular/fire/compat'; +import { AngularFireAuth, AngularFireAuthModule, SETTINGS, USE_EMULATOR } from '@angular/fire/compat/auth'; +import firebase from 'firebase/compat/app'; +import { Observable, Subject } from 'rxjs'; +import { COMMON_CONFIG, authEmulatorPort } from '../../../src/test-config'; +import 'firebase/compat/auth'; +import { rando } from '../../../src/utils'; + +const firebaseUser = { + uid: '12345', + providerData: [{ displayName: 'jeffbcrossyface' }] +} as firebase.User; + +describe('AngularFireAuth', () => { + let afAuth: AngularFireAuth; + let mockAuthState: Subject; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + AngularFireModule.initializeApp(COMMON_CONFIG, rando()), + AngularFireAuthModule + ], + providers: [ + { provide: SETTINGS, useValue: { appVerificationDisabledForTesting: true } }, + { provide: USE_EMULATOR, useValue: [`http://localhost:${authEmulatorPort}`] }, + ] + }); + + afAuth = TestBed.inject(AngularFireAuth); + + mockAuthState = new Subject(); + // @ts-ignore + spyOn(afAuth, 'authState').and.returnValue(mockAuthState); + // @ts-ignore + spyOn(afAuth, 'idToken').and.returnValue(mockAuthState); + (afAuth as any).authState = mockAuthState as Observable; + (afAuth as any).idToken = mockAuthState as Observable; + }); + + describe('Zones', () => { + it('should call operators and subscriber in the same zone as when service was initialized', (done) => { + // Initialize the app outside of the zone, to mimick real life behavior. + const ngZone = Zone.current.fork({ + name: 'ngZone' + }); + ngZone.run(() => { + const subs = [ + afAuth.authState.subscribe(() => { + expect(Zone.current.name).toBe('ngZone'); + done(); + }, done.fail), + // afAuth.authState.subscribe(() => { + // expect(Zone.current.name).toBe('ngZone'); + // done(); + // }, done.fail) + ]; + mockAuthState.next(firebaseUser); + subs.forEach(s => s.unsubscribe()); + }); + }); + }); + + it('should exist', () => { + expect(afAuth instanceof AngularFireAuth).toBe(true); + }); + + it('should have an initialized Firebase app', () => { + expect(afAuth.app).toBeDefined(); + }); + + it('should have disabled app verification for testing', async () => { + const app = await afAuth.app; + expect(app.auth().settings.appVerificationDisabledForTesting).toBe(true); + }); + + it('should emit auth updates through authState', (done: any) => { + let count = 0; + + // Check that the first value is null and second is the auth user + const subs = afAuth.authState.subscribe({ + next: (user => { + if (count === 0) { + expect(user).toBe(null); + count = count + 1; + mockAuthState.next(firebaseUser); + } else { + expect(user).toEqual(firebaseUser); + subs.unsubscribe(); + done(); + } + }), + error: done, + complete: done.fail + }); + mockAuthState.next(null); + }); + + it('should emit auth updates through idToken', (done: any) => { + let count = 0; + + // Check that the first value is null and second is the auth user + const subs = afAuth.idToken.subscribe({ + next: user => { + if (count === 0) { + expect(user).toBe(null); + count = count + 1; + mockAuthState.next(firebaseUser); + } else { + expect(user as any).toEqual(firebaseUser); + subs.unsubscribe(); + done(); + } + }, + error: done, + complete: done.fail + }); + mockAuthState.next(null); + }); + +}); + +describe('AngularFireAuth with different app', () => { + let app: FirebaseApp; + let afAuth: AngularFireAuth; + let firebaseAppName: string; + + beforeEach(() => { + firebaseAppName = rando(); + + TestBed.configureTestingModule({ + imports: [ + AngularFireModule.initializeApp(COMMON_CONFIG, rando()), + AngularFireAuthModule + ], + providers: [ + { provide: FIREBASE_APP_NAME, useValue: firebaseAppName }, + { provide: FIREBASE_OPTIONS, useValue: COMMON_CONFIG }, + { provide: USE_EMULATOR, useValue: [`http://localhost:${authEmulatorPort}`] }, + ] + }); + app = TestBed.inject(FirebaseApp); + afAuth = TestBed.inject(AngularFireAuth); + }); + + describe('', () => { + + it('should be an AngularFireAuth type', () => { + expect(afAuth instanceof AngularFireAuth).toEqual(true); + }); + + it('should have an initialized Firebase app', () => { + expect(afAuth.app).toBeDefined(); + }); + + it('should have an initialized Firebase app instance member', async () => { + const itsApp = await afAuth.app; + expect(itsApp).toEqual(app); + }); + }); + +}); diff --git a/src/compat/auth/auth.ts b/src/compat/auth/auth.ts new file mode 100644 index 000000000..87eb96e85 --- /dev/null +++ b/src/compat/auth/auth.ts @@ -0,0 +1,203 @@ +import { isPlatformServer } from '@angular/common'; +import { EnvironmentInjector, Inject, Injectable, InjectionToken, NgZone, Optional, PLATFORM_ID, inject } from '@angular/core'; +import { pendingUntilEvent } from '@angular/core/rxjs-interop'; +import { ÉĩAngularFireSchedulers } from '@angular/fire'; +import { AppCheckInstances } from '@angular/fire/app-check'; +import { ÉĩPromiseProxy, ÉĩapplyMixins, ÉĩlazySDKProxy } from '@angular/fire/compat'; +import { FIREBASE_APP_NAME, FIREBASE_OPTIONS, FirebaseApp, ÉĩcacheInstance, ÉĩfirebaseAppFactory } from '@angular/fire/compat'; +import { FirebaseOptions } from 'firebase/app'; +import firebase from 'firebase/compat/app'; +import { Observable, Subject, from, merge, of } from 'rxjs'; +import { filter, first, map, observeOn, shareReplay, subscribeOn, switchMap, switchMapTo } from 'rxjs/operators'; +import { proxyPolyfillCompat } from './base'; + + +export interface AngularFireAuth extends ÉĩPromiseProxy {} + +type UseEmulatorArguments = Parameters; +export const USE_EMULATOR = new InjectionToken('angularfire2.auth.use-emulator'); + +export const SETTINGS = new InjectionToken('angularfire2.auth.settings'); +export const TENANT_ID = new InjectionToken('angularfire2.auth.tenant-id'); +export const LANGUAGE_CODE = new InjectionToken('angularfire2.auth.langugage-code'); +export const USE_DEVICE_LANGUAGE = new InjectionToken('angularfire2.auth.use-device-language'); +export const PERSISTENCE = new InjectionToken('angularfire.auth.persistence'); + +export const ÉĩauthFactory = ( + app: FirebaseApp, zone: NgZone, useEmulator: UseEmulatorArguments|null, + tenantId: string, languageCode: string|null, useDeviceLanguage: boolean|null, + settings: firebase.auth.AuthSettings|null, persistence: string|null, +) => ÉĩcacheInstance(`${app.name}.auth`, 'AngularFireAuth', app.name, () => { + const auth = zone.runOutsideAngular(() => app.auth()); + if (useEmulator) { + auth.useEmulator(...useEmulator); + } + if (tenantId) { + auth.tenantId = tenantId; + } + auth.languageCode = languageCode; + if (useDeviceLanguage) { + auth.useDeviceLanguage(); + } + if (settings) { + for (const [k, v] of Object.entries(settings)) { + auth.settings[k] = v; + } + } + if (persistence) { + auth.setPersistence(persistence); + } + return auth; +}, [useEmulator, tenantId, languageCode, useDeviceLanguage, settings, persistence]); + +@Injectable({ + providedIn: 'any' +}) +export class AngularFireAuth { + + private readonly injector = inject(EnvironmentInjector); + + /** + * Observable of authentication state; as of Firebase 4.0 this is only triggered via sign-in/out + */ + public readonly authState: Observable; + + /** + * Observable of the currently signed-in user's JWT token used to identify the user to a Firebase service (or null). + */ + public readonly idToken: Observable; + + /** + * Observable of the currently signed-in user (or null). + */ + public readonly user: Observable; + + /** + * Observable of the currently signed-in user's IdTokenResult object which contains the ID token JWT string and other + * helper properties for getting different data associated with the token as well as all the decoded payload claims + * (or null). + */ + public readonly idTokenResult: Observable; + + /** + * Observable of the currently signed-in user's credential, or null + */ + public readonly credential: Observable|null>; + + constructor( + @Inject(FIREBASE_OPTIONS) options: FirebaseOptions, + @Optional() @Inject(FIREBASE_APP_NAME) name: string|null|undefined, + + @Inject(PLATFORM_ID) platformId: object, + zone: NgZone, + schedulers: ÉĩAngularFireSchedulers, + @Optional() @Inject(USE_EMULATOR) useEmulator: any, // can't use the tuple here + @Optional() @Inject(SETTINGS) settings: any, // can't use firebase.auth.AuthSettings here + @Optional() @Inject(TENANT_ID) tenantId: string | null, + @Optional() @Inject(LANGUAGE_CODE) languageCode: string | null, + @Optional() @Inject(USE_DEVICE_LANGUAGE) useDeviceLanguage: boolean | null, + @Optional() @Inject(PERSISTENCE) persistence: string | null, + @Optional() _appCheckInstances: AppCheckInstances, + ) { + const logins = new Subject>(); + + const auth = of(undefined).pipe( + observeOn(schedulers.outsideAngular), + switchMap(() => zone.runOutsideAngular(() => import('firebase/compat/auth'))), + map(() => ÉĩfirebaseAppFactory(options, zone, name)), + map(app => ÉĩauthFactory(app, zone, useEmulator, tenantId, languageCode, useDeviceLanguage, settings, persistence)), + shareReplay({ bufferSize: 1, refCount: false }), + ); + + if (isPlatformServer(platformId)) { + + this.authState = this.user = this.idToken = this.idTokenResult = this.credential = of(null); + + } else { + + // HACK, as we're exporting auth.Auth, rather than auth, developers importing firebase.auth + // (e.g, `import { auth } from 'firebase/compat/app'`) are getting an undefined auth object unexpectedly + // as we're completely lazy. Let's eagerly load the Auth SDK here. + // There could potentially be race conditions still... but this greatly decreases the odds while + // we reevaluate the API. + auth.pipe(first()).subscribe(); + + const redirectResult = auth.pipe( + switchMap(auth => auth.getRedirectResult().then(it => it, () => null)), + pendingUntilEvent(this.injector), + shareReplay({ bufferSize: 1, refCount: false }), + ); + + const authStateChanged = auth.pipe( + switchMap(auth => new Observable(sub => + ({ unsubscribe: zone.runOutsideAngular(() => auth.onAuthStateChanged( + next => sub.next(next), + err => sub.error(err), + () => sub.complete() + ))}) + )), + ); + + const idTokenChanged = auth.pipe( + switchMap(auth => new Observable(sub => + ({ unsubscribe: zone.runOutsideAngular(() => auth.onIdTokenChanged( + next => sub.next(next), + err => sub.error(err), + () => sub.complete() + ))}) + )) + ); + + this.authState = redirectResult.pipe( + switchMapTo(authStateChanged), + subscribeOn(schedulers.outsideAngular), + observeOn(schedulers.insideAngular), + ); + + this.user = redirectResult.pipe( + switchMapTo(idTokenChanged), + subscribeOn(schedulers.outsideAngular), + observeOn(schedulers.insideAngular), + ); + + this.idToken = this.user.pipe( + switchMap(user => user ? from(user.getIdToken()) : of(null)) + ); + + this.idTokenResult = this.user.pipe( + switchMap(user => user ? from(user.getIdTokenResult()) : of(null)) + ); + + this.credential = merge( + redirectResult, + logins, + // pipe in null authState to make credential zipable, just a weird devexp if + // authState and user go null to still have a credential + this.authState.pipe(filter(it => !it)) + ).pipe( + // handle the { user: { } } when a user is already logged in, rather have null + // TODO handle the type corcersion better + map(credential => credential?.user ? credential as Required : null), + subscribeOn(schedulers.outsideAngular), + observeOn(schedulers.insideAngular), + ); + + } + + return ÉĩlazySDKProxy(this, auth, zone, { spy: { + apply: (name, _, val) => { + // If they call a signIn or createUser function listen into the promise + // this will give us the user credential, push onto the logins Subject + // to be consumed in .credential + if (name.startsWith('signIn') || name.startsWith('createUser')) { + // TODO fix the types, the trouble is UserCredential has everything optional + val.then((user: firebase.auth.UserCredential) => logins.next(user as any)); + } + } + }}); + + } + +} + +ÉĩapplyMixins(AngularFireAuth, [proxyPolyfillCompat]); diff --git a/src/compat/auth/base.ts b/src/compat/auth/base.ts new file mode 100644 index 000000000..9b1e84b18 --- /dev/null +++ b/src/compat/auth/base.ts @@ -0,0 +1,38 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +// Export a null object with the same keys as firebase/compat/auth, so Proxy can work with proxy-polyfill in Internet Explorer +export const proxyPolyfillCompat = { + name: null, + config: null, + emulatorConfig: null, + app: null, + applyActionCode: null, + checkActionCode: null, + confirmPasswordReset: null, + createUserWithEmailAndPassword: null, + currentUser: null, + fetchSignInMethodsForEmail: null, + isSignInWithEmailLink: null, + getRedirectResult: null, + languageCode: null, + settings: null, + onAuthStateChanged: null, + onIdTokenChanged: null, + sendSignInLinkToEmail: null, + sendPasswordResetEmail: null, + setPersistence: null, + signInAndRetrieveDataWithCredential: null, + signInAnonymously: null, + signInWithCredential: null, + signInWithCustomToken: null, + signInWithEmailAndPassword: null, + signInWithPhoneNumber: null, + signInWithEmailLink: null, + signInWithPopup: null, + signInWithRedirect: null, + signOut: null, + tenantId: null, + updateCurrentUser: null, + useDeviceLanguage: null, + useEmulator: null, + verifyPasswordResetCode: null, +}; diff --git a/src/compat/auth/ng-package.json b/src/compat/auth/ng-package.json new file mode 100644 index 000000000..e5e5ccdfc --- /dev/null +++ b/src/compat/auth/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/compat/auth/package.json b/src/compat/auth/package.json new file mode 100644 index 000000000..5eaf9d5f0 --- /dev/null +++ b/src/compat/auth/package.json @@ -0,0 +1,3 @@ +{ + "$schema": "../../../node_modules/ng-packagr/package.schema.json" +} diff --git a/src/compat/auth/public_api.ts b/src/compat/auth/public_api.ts new file mode 100644 index 000000000..9948840d5 --- /dev/null +++ b/src/compat/auth/public_api.ts @@ -0,0 +1,5 @@ + +import 'firebase/compat/auth'; // removed in build process when not UMD + +export * from './auth'; +export * from './auth.module'; diff --git a/src/compat/cache.ts b/src/compat/cache.ts new file mode 100644 index 000000000..108b6beaf --- /dev/null +++ b/src/compat/cache.ts @@ -0,0 +1,35 @@ +import { isDevMode } from '@angular/core'; + +export function ÉĩcacheInstance(cacheKey: any, moduleName: string, appName: string, fn: () => T, deps: any): T { + const [, instance, cachedDeps] = globalThis.ÉĩAngularfireInstanceCache.find((it: any) => it[0] === cacheKey) || []; + if (instance) { + if (!matchDep(deps, cachedDeps)) { + log('error', `${moduleName} was already initialized on the ${appName} Firebase App with different settings.${IS_HMR ? ' You may need to reload as Firebase is not HMR aware.' : ''}`); + log('warn', {is: deps, was: cachedDeps}); + } + return instance; + } else { + const newInstance = fn(); + globalThis.ÉĩAngularfireInstanceCache.push([cacheKey, newInstance, deps]); + return newInstance; + } +} + +function matchDep(a: any, b: any) { + try { + return a.toString() === b.toString(); + } catch (_) { + return a === b; + } +} + +const IS_HMR = typeof module !== 'undefined' && !!(module as any).hot; + +const log = (level: 'log'|'error'|'info'|'warn', ...args: any) => { + if (isDevMode() && typeof console !== 'undefined') { + // eslint-disable-next-line no-console + console[level](...args); + } +}; + +globalThis.ÉĩAngularfireInstanceCache ||= []; diff --git a/src/compat/database/database.module.ts b/src/compat/database/database.module.ts new file mode 100644 index 000000000..3c1d51d32 --- /dev/null +++ b/src/compat/database/database.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { VERSION } from '@angular/fire'; +import firebase from 'firebase/compat/app'; +import { AngularFireDatabase } from './database'; + +@NgModule({ + providers: [ AngularFireDatabase ] +}) +export class AngularFireDatabaseModule { + constructor() { + firebase.registerVersion('angularfire', VERSION.full, 'rtdb-compat'); + } +} diff --git a/src/compat/database/database.spec.ts b/src/compat/database/database.spec.ts new file mode 100644 index 000000000..09f3d0990 --- /dev/null +++ b/src/compat/database/database.spec.ts @@ -0,0 +1,113 @@ +import { NgZone } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { ÉĩAngularFireSchedulers } from '@angular/fire'; +import { AngularFireModule, FIREBASE_APP_NAME, FIREBASE_OPTIONS, FirebaseApp } from '@angular/fire/compat'; +import { AngularFireDatabase, AngularFireDatabaseModule, USE_EMULATOR } from '@angular/fire/compat/database'; +import 'firebase/compat/database'; +import { COMMON_CONFIG, databaseEmulatorPort } from '../../../src/test-config'; +import { rando } from '../../../src/utils'; + +describe('AngularFireDatabase', () => { + let app: FirebaseApp; + let db: AngularFireDatabase; + let zone: NgZone; + let firebaseAppName: string; + + beforeEach(() => { + firebaseAppName = rando(); + TestBed.configureTestingModule({ + imports: [ + AngularFireModule.initializeApp(COMMON_CONFIG, firebaseAppName), + AngularFireDatabaseModule + ], + providers: [ + { provide: USE_EMULATOR, useValue: ['localhost', databaseEmulatorPort] } + ] + }); + + app = TestBed.inject(FirebaseApp); + db = TestBed.inject(AngularFireDatabase); + zone = TestBed.inject(NgZone); + }); + + describe('', () => { + + it('should be an AngularFireDatabase type', () => { + expect(db instanceof AngularFireDatabase).toEqual(true); + }); + + it('should have an initialized Firebase app', () => { + expect(db.database.app).toBeDefined(); + }); + + it('should accept a Firebase App in the constructor', (done) => { + const schedulers = TestBed.runInInjectionContext(() => new ÉĩAngularFireSchedulers()); + const database = TestBed.runInInjectionContext(() => new AngularFireDatabase( + app.options, rando(), undefined, {}, zone, schedulers, undefined, undefined, + undefined, undefined, undefined, undefined, undefined, undefined, undefined, + )); + expect(database instanceof AngularFireDatabase).toEqual(true); + // try { database.database.app.delete().then(done, done); } catch(e) { done(); } + done(); + }); + + it('should have an initialized Firebase app instance member', () => { + expect(db.database.app.name).toEqual(firebaseAppName); + }); + + }); + +}); + +describe('AngularFireDatabase w/options', () => { + let db: AngularFireDatabase; + let firebaseAppName: string; + + beforeEach(() => { + firebaseAppName = rando(); + TestBed.configureTestingModule({ + imports: [ + AngularFireModule.initializeApp(COMMON_CONFIG, rando()), + AngularFireDatabaseModule + ], + providers: [ + { provide: FIREBASE_APP_NAME, useValue: firebaseAppName }, + { provide: FIREBASE_OPTIONS, useValue: COMMON_CONFIG }, + { provide: USE_EMULATOR, useValue: ['localhost', databaseEmulatorPort] } + ] + }); + + db = TestBed.inject(AngularFireDatabase); + }); + + describe('', () => { + + it('should be an AngularFireDatabase type', () => { + expect(db instanceof AngularFireDatabase).toEqual(true); + }); + + it('should have an initialized Firebase app', () => { + expect(db.database.app).toBeDefined(); + }); + + it('should have an initialized Firebase app instance member', () => { + expect(db.database.app.name).toEqual(firebaseAppName); + }); + + /* INVESTIGATE database(url) does not seem to be working + + it('database be pointing to the provided DB instance', () => { + expect(db.database.ref().toString()).toEqual(url); + }); + + it('list should be using the provided DB instance', () => { + expect(db.list(query).query.toString()).toEqual(`${url}/${query}`); + }); + + it('object should be using the provided DB instance', () => { + expect(db.object(query).query.toString()).toEqual(`${url}/${query}`); + }); + */ + }); + +}); diff --git a/src/compat/database/database.ts b/src/compat/database/database.ts new file mode 100644 index 000000000..d03572941 --- /dev/null +++ b/src/compat/database/database.ts @@ -0,0 +1,103 @@ +import { EnvironmentInjector, Inject, Injectable, InjectionToken, NgZone, Optional, PLATFORM_ID, inject } from '@angular/core'; +import { ÉĩAngularFireSchedulers } from '@angular/fire'; +import { AppCheckInstances } from '@angular/fire/app-check'; +import { FIREBASE_APP_NAME, FIREBASE_OPTIONS, ÉĩcacheInstance, ÉĩfirebaseAppFactory } from '@angular/fire/compat'; +import { + SETTINGS as AUTH_SETTINGS, + AngularFireAuth, + LANGUAGE_CODE, + PERSISTENCE, + TENANT_ID, + USE_EMULATOR as USE_AUTH_EMULATOR, + USE_DEVICE_LANGUAGE, + ÉĩauthFactory, +} from '@angular/fire/compat/auth'; +import { FirebaseOptions } from 'firebase/app'; +import firebase from 'firebase/compat/app'; +import { AngularFireList, AngularFireObject, DatabaseQuery, PathReference, QueryFn } from './interfaces'; +import { createListReference } from './list/create-reference'; +import { createObjectReference } from './object/create-reference'; +import { getRef } from './utils'; +import 'firebase/compat/auth'; +import 'firebase/compat/database'; + +export const URL = new InjectionToken('angularfire2.realtimeDatabaseURL'); + +type UseEmulatorArguments = Parameters; +export const USE_EMULATOR = new InjectionToken('angularfire2.database.use-emulator'); + +@Injectable({ + providedIn: 'any' +}) +export class AngularFireDatabase { + public readonly database: firebase.database.Database; + private readonly injector = inject(EnvironmentInjector); + + constructor( + @Inject(FIREBASE_OPTIONS) options: FirebaseOptions, + @Optional() @Inject(FIREBASE_APP_NAME) name: string | null | undefined, + @Optional() @Inject(URL) databaseURL: string | null, + + @Inject(PLATFORM_ID) platformId: object, + zone: NgZone, + public schedulers: ÉĩAngularFireSchedulers, + @Optional() @Inject(USE_EMULATOR) _useEmulator: any, // tuple isn't working here + @Optional() auth: AngularFireAuth, + @Optional() @Inject(USE_AUTH_EMULATOR) useAuthEmulator: any, + @Optional() @Inject(AUTH_SETTINGS) authSettings: any, // can't use firebase.auth.AuthSettings here + @Optional() @Inject(TENANT_ID) tenantId: string | null, + @Optional() @Inject(LANGUAGE_CODE) languageCode: string | null, + @Optional() @Inject(USE_DEVICE_LANGUAGE) useDeviceLanguage: boolean | null, + @Optional() @Inject(PERSISTENCE) persistence: string | null, + @Optional() _appCheckInstances: AppCheckInstances, + ) { + + const useEmulator: UseEmulatorArguments | null = _useEmulator; + const app = ÉĩfirebaseAppFactory(options, zone, name); + + if (auth) { + ÉĩauthFactory(app, zone, useAuthEmulator, tenantId, languageCode, useDeviceLanguage, authSettings, persistence); + } + + this.database = ÉĩcacheInstance(`${app.name}.database.${databaseURL}`, 'AngularFireDatabase', app.name, () => { + const database = zone.runOutsideAngular(() => app.database(databaseURL || undefined)); + if (useEmulator) { + database.useEmulator(...useEmulator); + } + return database; + }, [useEmulator]); + } + + list(pathOrRef: PathReference, queryFn?: QueryFn): AngularFireList { + const ref = inject(NgZone).runOutsideAngular(() => getRef(this.database, pathOrRef)); + let query: DatabaseQuery = ref; + if (queryFn) { + query = queryFn(ref); + } + return createListReference(query, this, this.injector); + } + + object(pathOrRef: PathReference): AngularFireObject { + const ref = inject(NgZone).runOutsideAngular(() => getRef(this.database, pathOrRef)); + return createObjectReference(ref, this, this.injector); + } + + createPushId() { + const ref = inject(NgZone).runOutsideAngular(() => this.database.ref()); + return ref.push().key; + } + +} + +export { + PathReference, + DatabaseSnapshot, + ChildEvent, + ListenEvent, + QueryFn, + AngularFireList, + AngularFireObject, + AngularFireAction, + Action, + SnapshotAction +} from './interfaces'; diff --git a/src/database/interfaces.ts b/src/compat/database/interfaces.ts similarity index 71% rename from src/database/interfaces.ts rename to src/compat/database/interfaces.ts index e1ff15b3a..99c2d651a 100644 --- a/src/database/interfaces.ts +++ b/src/compat/database/interfaces.ts @@ -1,17 +1,18 @@ +import firebase from 'firebase/compat/app'; import { Observable } from 'rxjs'; -import { database } from 'firebase/app'; -export type FirebaseOperation = string | database.Reference | database.DataSnapshot; +export type FirebaseOperation = string | firebase.database.Reference | firebase.database.DataSnapshot; export interface AngularFireList { query: DatabaseQuery; - valueChanges(events?: ChildEvent[]): Observable; + valueChanges(events?: ChildEvent[], options?: unknown): Observable; + valueChanges(events?: ChildEvent[], options?: {idField: K}): Observable<(T & Partial>)[]>; snapshotChanges(events?: ChildEvent[]): Observable[]>; stateChanges(events?: ChildEvent[]): Observable>; auditTrail(events?: ChildEvent[]): Observable[]>; update(item: FirebaseOperation, data: Partial): Promise; set(item: FirebaseOperation, data: T): Promise; - push(data: T): database.ThenableReference; + push(data: T): firebase.database.ThenableReference; remove(item?: FirebaseOperation): Promise; } @@ -49,13 +50,13 @@ export type SnapshotAction = AngularFireAction>; export type Primitive = number | string | boolean; -export interface DatabaseSnapshotExists extends database.DataSnapshot { +export interface DatabaseSnapshotExists extends firebase.database.DataSnapshot { exists(): true; val(): T; forEach(action: (a: DatabaseSnapshot) => boolean): boolean; } -export interface DatabaseSnapshotDoesNotExist extends database.DataSnapshot { +export interface DatabaseSnapshotDoesNotExist extends firebase.database.DataSnapshot { exists(): false; val(): null; forEach(action: (a: DatabaseSnapshot) => boolean): boolean; @@ -63,8 +64,8 @@ export interface DatabaseSnapshotDoesNotExist extends database.DataSnapshot { export type DatabaseSnapshot = DatabaseSnapshotExists | DatabaseSnapshotDoesNotExist; -export type DatabaseReference = database.Reference; -export type DatabaseQuery = database.Query; -export type DataSnapshot = database.DataSnapshot; +export type DatabaseReference = firebase.database.Reference; +export type DatabaseQuery = firebase.database.Query; +export type DataSnapshot = firebase.database.DataSnapshot; export type QueryReference = DatabaseReference | DatabaseQuery; export type PathReference = QueryReference | string; diff --git a/src/database/list/audit-trail.spec.ts b/src/compat/database/list/audit-trail.spec.ts similarity index 70% rename from src/database/list/audit-trail.spec.ts rename to src/compat/database/list/audit-trail.spec.ts index c866417a5..a462d50cf 100644 --- a/src/database/list/audit-trail.spec.ts +++ b/src/compat/database/list/audit-trail.spec.ts @@ -1,16 +1,15 @@ -import { DatabaseReference } from '../interfaces'; -import { AngularFireModule, FirebaseApp } from '@angular/fire'; -import { AngularFireDatabase, AngularFireDatabaseModule, auditTrail, ChildEvent, URL } from '../public_api'; import { TestBed } from '@angular/core/testing'; -import { COMMON_CONFIG } from '../../test-config'; +import { AngularFireModule } from '@angular/fire/compat'; +import { AngularFireDatabase, AngularFireDatabaseModule, ChildEvent, USE_EMULATOR, auditTrail } from '@angular/fire/compat/database'; +import firebase from 'firebase/compat/app'; import { skip } from 'rxjs/operators'; -import 'firebase/database'; -import { rando } from '../../firestore/utils.spec'; +import { COMMON_CONFIG, databaseEmulatorPort } from '../../../../src/test-config'; +import 'firebase/compat/database'; +import { rando } from '../../../../src/utils'; describe('auditTrail', () => { - let app: FirebaseApp; let db: AngularFireDatabase; - let createRef: (path: string) => DatabaseReference; + let createRef: (path: string) => firebase.database.Reference; let batch = {}; const items = [{ name: 'zero' }, { name: 'one' }, { name: 'two' }].map((item, i) => ({ key: i.toString(), ...item })); Object.keys(items).forEach((key, i) => { @@ -26,19 +25,14 @@ describe('auditTrail', () => { AngularFireDatabaseModule ], providers: [ - { provide: URL, useValue: 'http://localhost:9000' } + { provide: USE_EMULATOR, useValue: ['localhost', databaseEmulatorPort] } ] }); - app = TestBed.inject(FirebaseApp); db = TestBed.inject(AngularFireDatabase); createRef = (path: string) => db.database.ref(path); }); - afterEach(() => { - app.delete(); - }); - function prepareAuditTrail(opts: { events?: ChildEvent[], skipnumber: number } = { skipnumber: 0 }) { const { events, skipnumber } = opts; const aref = createRef(rando()); diff --git a/src/database/list/audit-trail.ts b/src/compat/database/list/audit-trail.ts similarity index 92% rename from src/database/list/audit-trail.ts rename to src/compat/database/list/audit-trail.ts index 1ead7342b..1d9f2e280 100644 --- a/src/database/list/audit-trail.ts +++ b/src/compat/database/list/audit-trail.ts @@ -1,9 +1,9 @@ -import { AngularFireAction, ChildEvent, DatabaseQuery, DataSnapshot, SnapshotAction } from '../interfaces'; -import { stateChanges } from './state-changes'; import { Observable, SchedulerLike } from 'rxjs'; +import { map, scan, skipWhile, withLatestFrom } from 'rxjs/operators'; +import { AngularFireAction, ChildEvent, DataSnapshot, DatabaseQuery, SnapshotAction } from '../interfaces'; import { fromRef } from '../observable/fromRef'; +import { stateChanges } from './state-changes'; -import { map, scan, skipWhile, withLatestFrom } from 'rxjs/operators'; export function auditTrail(query: DatabaseQuery, events?: ChildEvent[], scheduler?: SchedulerLike): Observable[]> { const auditTrail$ = stateChanges(query, events) @@ -54,7 +54,7 @@ function waitForLoaded(query: DatabaseQuery, action$: Observable meta.loadedKeys.indexOf(meta.lastKeyToLoad) === -1), + skipWhile(meta => !meta.loadedKeys.includes(meta.lastKeyToLoad)), // Pluck off the meta data because the user only cares // to iterate through the snapshots map(meta => meta.actions) diff --git a/src/database/list/changes.spec.ts b/src/compat/database/list/changes.spec.ts similarity index 89% rename from src/database/list/changes.spec.ts rename to src/compat/database/list/changes.spec.ts index c21c2a8bb..0dd368a5f 100644 --- a/src/database/list/changes.spec.ts +++ b/src/compat/database/list/changes.spec.ts @@ -1,16 +1,15 @@ -import { database } from 'firebase/app'; -import { AngularFireModule, FirebaseApp } from '@angular/fire'; -import { AngularFireDatabase, AngularFireDatabaseModule, listChanges, URL } from '../public_api'; import { TestBed } from '@angular/core/testing'; -import { COMMON_CONFIG } from '../../test-config'; +import { AngularFireModule } from '@angular/fire/compat'; +import { AngularFireDatabase, AngularFireDatabaseModule, USE_EMULATOR, listChanges } from '@angular/fire/compat/database'; +import firebase from 'firebase/compat/app'; import { skip, take } from 'rxjs/operators'; -import 'firebase/database'; -import { rando } from '../../firestore/utils.spec'; +import { COMMON_CONFIG, databaseEmulatorPort } from '../../../../src/test-config'; +import 'firebase/compat/database'; +import { rando } from '../../../../src/utils'; describe('listChanges', () => { - let app: FirebaseApp; let db: AngularFireDatabase; - let ref: (path: string) => database.Reference; + let ref: (path: string) => firebase.database.Reference; let batch = {}; const items = [{ name: 'zero' }, { name: 'one' }, { name: 'two' }].map((item, i) => ({ key: i.toString(), ...item })); Object.keys(items).forEach((key, i) => { @@ -26,19 +25,14 @@ describe('listChanges', () => { AngularFireDatabaseModule ], providers: [ - { provide: URL, useValue: 'http://localhost:9000' } + { provide: USE_EMULATOR, useValue: ['localhost', databaseEmulatorPort] } ] }); - app = TestBed.inject(FirebaseApp); db = TestBed.inject(AngularFireDatabase); ref = (path: string) => db.database.ref(path); }); - afterEach(() => { - app.delete(); - }); - describe('events', () => { it('should stream value at first', (done) => { diff --git a/src/database/list/changes.ts b/src/compat/database/list/changes.ts similarity index 94% rename from src/database/list/changes.ts rename to src/compat/database/list/changes.ts index cb37d0694..c7c5fd904 100644 --- a/src/database/list/changes.ts +++ b/src/compat/database/list/changes.ts @@ -1,10 +1,9 @@ -import { fromRef } from '../observable/fromRef'; -import { merge, Observable, of, SchedulerLike } from 'rxjs'; - +import { Observable, SchedulerLike, merge, of } from 'rxjs'; +import { distinctUntilChanged, scan, switchMap } from 'rxjs/operators'; import { ChildEvent, DatabaseQuery, SnapshotAction } from '../interfaces'; +import { fromRef } from '../observable/fromRef'; import { isNil } from '../utils'; -import { distinctUntilChanged, scan, switchMap } from 'rxjs/operators'; export function listChanges(ref: DatabaseQuery, events: ChildEvent[], scheduler?: SchedulerLike): Observable[]> { return fromRef(ref, 'value', 'once', scheduler).pipe( @@ -46,7 +45,7 @@ function buildView(current, action) { const afterPreviousKeyPosition = positionAfter(current, prevKey); switch (action.type) { case 'value': - if (action.payload && action.payload.exists()) { + if (action.payload?.exists()) { let prevKey = null; action.payload.forEach(payload => { const action = { payload, type: 'value', prevKey, key: payload.key }; @@ -60,7 +59,7 @@ function buildView(current, action) { if (currentKeyPosition > -1) { // check that the previouskey is what we expect, else reorder const previous = current[currentKeyPosition - 1]; - if ((previous && previous.key || null) !== prevKey) { + if ((previous?.key || null) !== prevKey) { current = current.filter(x => x.payload.key !== payload.key); current.splice(afterPreviousKeyPosition, 0, action); } diff --git a/src/database/list/create-reference.ts b/src/compat/database/list/create-reference.ts similarity index 52% rename from src/database/list/create-reference.ts rename to src/compat/database/list/create-reference.ts index b80136a62..f8f5cce34 100644 --- a/src/database/list/create-reference.ts +++ b/src/compat/database/list/create-reference.ts @@ -1,35 +1,49 @@ +import { Injector, NgZone, inject } from '@angular/core'; +import { pendingUntilEvent } from '@angular/core/rxjs-interop'; +import type { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { AngularFireDatabase } from '../database'; import { AngularFireList, ChildEvent, DatabaseQuery } from '../interfaces'; -import { snapshotChanges } from './snapshot-changes'; -import { stateChanges } from './state-changes'; import { auditTrail } from './audit-trail'; import { createDataOperationMethod } from './data-operation'; import { createRemoveMethod } from './remove'; -import { AngularFireDatabase } from '../database'; -import { map } from 'rxjs/operators'; +import { snapshotChanges } from './snapshot-changes'; +import { stateChanges } from './state-changes'; -export function createListReference(query: DatabaseQuery, afDatabase: AngularFireDatabase): AngularFireList { +export function createListReference(query: DatabaseQuery, afDatabase: AngularFireDatabase, injector?: Injector): AngularFireList { const outsideAngularScheduler = afDatabase.schedulers.outsideAngular; - const refInZone = afDatabase.schedulers.ngZone.run(() => query.ref); + const refInZone = inject(NgZone).run(() => query.ref); return { query, - update: createDataOperationMethod>(refInZone, 'update'), - set: createDataOperationMethod(refInZone, 'set'), + update: createDataOperationMethod(refInZone, 'update'), + set: createDataOperationMethod(refInZone, 'set'), push: (data: T) => refInZone.push(data), remove: createRemoveMethod(refInZone), snapshotChanges(events?: ChildEvent[]) { - return snapshotChanges(query, events, outsideAngularScheduler).pipe(afDatabase.keepUnstableUntilFirst); + return snapshotChanges(query, events, outsideAngularScheduler).pipe(pendingUntilEvent(injector)); }, stateChanges(events?: ChildEvent[]) { - return stateChanges(query, events, outsideAngularScheduler).pipe(afDatabase.keepUnstableUntilFirst); + return stateChanges(query, events, outsideAngularScheduler).pipe(pendingUntilEvent(injector)); }, auditTrail(events?: ChildEvent[]) { - return auditTrail(query, events, outsideAngularScheduler).pipe(afDatabase.keepUnstableUntilFirst); + return auditTrail(query, events, outsideAngularScheduler).pipe(pendingUntilEvent(injector)); }, - valueChanges(events?: ChildEvent[]) { + valueChanges(events?: ChildEvent[], options?: {idField?: K}): Observable<(T & Record)[]> { const snapshotChanges$ = snapshotChanges(query, events, outsideAngularScheduler); return snapshotChanges$.pipe( - map(actions => actions.map(a => a.payload.val() as T)), - afDatabase.keepUnstableUntilFirst + map(actions => actions.map(a => { + if (options?.idField) { + return { + ...a.payload.val() as T, + ...{ + [options.idField]: a.key + } + }; + } else { + return a.payload.val() as T & Record + } + })), + pendingUntilEvent(injector) ); } }; diff --git a/src/database/list/data-operation.ts b/src/compat/database/list/data-operation.ts similarity index 84% rename from src/database/list/data-operation.ts rename to src/compat/database/list/data-operation.ts index 78bb0bf4e..16650a8b3 100644 --- a/src/database/list/data-operation.ts +++ b/src/compat/database/list/data-operation.ts @@ -1,7 +1,7 @@ import { DatabaseReference, DatabaseSnapshot, FirebaseOperation } from '../interfaces'; import { checkOperationCases } from '../utils'; -export function createDataOperationMethod(ref: DatabaseReference, operation: string) { +export function createDataOperationMethod(ref: DatabaseReference, operation: string) { return function dataOperation(item: FirebaseOperation, value: T) { return checkOperationCases(item, { stringCase: () => ref.child(item as string)[operation](value), diff --git a/src/database/list/remove.ts b/src/compat/database/list/remove.ts similarity index 100% rename from src/database/list/remove.ts rename to src/compat/database/list/remove.ts diff --git a/src/database/list/snapshot-changes.spec.ts b/src/compat/database/list/snapshot-changes.spec.ts similarity index 86% rename from src/database/list/snapshot-changes.spec.ts rename to src/compat/database/list/snapshot-changes.spec.ts index 6ba1959ea..6a82eeda2 100644 --- a/src/database/list/snapshot-changes.spec.ts +++ b/src/compat/database/list/snapshot-changes.spec.ts @@ -1,17 +1,16 @@ -import { database } from 'firebase/app'; -import { AngularFireModule, FirebaseApp } from '@angular/fire'; -import { AngularFireDatabase, AngularFireDatabaseModule, ChildEvent, snapshotChanges, URL } from '../public_api'; import { TestBed } from '@angular/core/testing'; -import { COMMON_CONFIG } from '../../test-config'; +import { AngularFireModule } from '@angular/fire/compat'; +import { AngularFireDatabase, AngularFireDatabaseModule, ChildEvent, USE_EMULATOR, snapshotChanges } from '@angular/fire/compat/database'; +import firebase from 'firebase/compat/app'; import { BehaviorSubject } from 'rxjs'; import { skip, switchMap, take } from 'rxjs/operators'; -import 'firebase/database'; -import { rando } from '../../firestore/utils.spec'; +import { COMMON_CONFIG, databaseEmulatorPort } from '../../../../src/test-config'; +import 'firebase/compat/database'; +import { rando } from '../../../../src/utils'; describe('snapshotChanges', () => { - let app: FirebaseApp; let db: AngularFireDatabase; - let createRef: (path: string) => database.Reference; + let createRef: (path: string) => firebase.database.Reference; let batch = {}; const items = [{ name: 'zero' }, { name: 'one' }, { name: 'two' }].map((item, i) => ({ key: i.toString(), ...item })); Object.keys(items).forEach((key, i) => { @@ -27,19 +26,14 @@ describe('snapshotChanges', () => { AngularFireDatabaseModule ], providers: [ - { provide: URL, useValue: 'http://localhost:9000' } + { provide: USE_EMULATOR, useValue: ['localhost', databaseEmulatorPort] } ] }); - app = TestBed.inject(FirebaseApp); db = TestBed.inject(AngularFireDatabase); createRef = (path: string) => db.database.ref(path); }); - afterEach(() => { - app.delete(); - }); - function prepareSnapshotChanges(opts: { events?: ChildEvent[], skipnumber: number } = { skipnumber: 0 }) { const { events, skipnumber } = opts; const aref = createRef(rando()); @@ -61,8 +55,8 @@ describe('snapshotChanges', () => { it('should handle multiple subscriptions (hot)', (done) => { const { snapChanges, ref } = prepareSnapshotChanges(); - const sub = snapChanges.subscribe(() => { - }).add(done); + const sub = snapChanges.subscribe(() => undefined); + sub.add(done); snapChanges.pipe(take(1)).subscribe(actions => { const data = actions.map(a => a.payload.val()); expect(data).toEqual(items); @@ -72,8 +66,7 @@ describe('snapshotChanges', () => { it('should handle multiple subscriptions (warm)', done => { const { snapChanges, ref } = prepareSnapshotChanges(); - snapChanges.pipe(take(1)).subscribe(() => { - }).add(() => { + snapChanges.pipe(take(1)).subscribe(() => undefined).add(() => { snapChanges.pipe(take(1)).subscribe(actions => { const data = actions.map(a => a.payload.val()); expect(data).toEqual(items); diff --git a/src/database/list/snapshot-changes.ts b/src/compat/database/list/snapshot-changes.ts similarity index 100% rename from src/database/list/snapshot-changes.ts rename to src/compat/database/list/snapshot-changes.ts index 7422c65bd..a98adf045 100644 --- a/src/database/list/snapshot-changes.ts +++ b/src/compat/database/list/snapshot-changes.ts @@ -1,6 +1,6 @@ import { Observable, SchedulerLike } from 'rxjs'; -import { listChanges } from './changes'; import { ChildEvent, DatabaseQuery, SnapshotAction } from '../interfaces'; +import { listChanges } from './changes'; import { validateEventsArray } from './utils'; export function snapshotChanges( diff --git a/src/database/list/state-changes.spec.ts b/src/compat/database/list/state-changes.spec.ts similarity index 74% rename from src/database/list/state-changes.spec.ts rename to src/compat/database/list/state-changes.spec.ts index b6d3c2711..86855ac09 100644 --- a/src/database/list/state-changes.spec.ts +++ b/src/compat/database/list/state-changes.spec.ts @@ -1,16 +1,15 @@ -import { database } from 'firebase/app'; -import { AngularFireModule, FirebaseApp } from '@angular/fire'; -import { AngularFireDatabase, AngularFireDatabaseModule, ChildEvent, stateChanges, URL } from '../public_api'; import { TestBed } from '@angular/core/testing'; -import { COMMON_CONFIG } from '../../test-config'; +import { AngularFireModule } from '@angular/fire/compat'; +import { AngularFireDatabase, AngularFireDatabaseModule, ChildEvent, USE_EMULATOR, stateChanges } from '@angular/fire/compat/database'; +import firebase from 'firebase/compat/app'; import { skip } from 'rxjs/operators'; -import 'firebase/database'; -import { rando } from '../../firestore/utils.spec'; +import { COMMON_CONFIG, databaseEmulatorPort } from '../../../../src/test-config'; +import 'firebase/compat/database'; +import { rando } from '../../../../src/utils'; describe('stateChanges', () => { - let app: FirebaseApp; let db: AngularFireDatabase; - let createRef: (path: string) => database.Reference; + let createRef: (path: string) => firebase.database.Reference; let batch = {}; const items = [{ name: 'zero' }, { name: 'one' }, { name: 'two' }].map((item, i) => ({ key: i.toString(), ...item })); Object.keys(items).forEach((key, i) => { @@ -26,19 +25,14 @@ describe('stateChanges', () => { AngularFireDatabaseModule ], providers: [ - { provide: URL, useValue: 'http://localhost:9000' } + { provide: USE_EMULATOR, useValue: ['localhost', databaseEmulatorPort] } ] }); - app = TestBed.inject(FirebaseApp); db = TestBed.inject(AngularFireDatabase); createRef = (path: string) => db.database.ref(path); }); - afterEach(() => { - app.delete(); - }); - function prepareStateChanges(opts: { events?: ChildEvent[], skipnumber: number } = { skipnumber: 0 }) { const { events, skipnumber } = opts; const aref = createRef(rando()); diff --git a/src/database/list/state-changes.ts b/src/compat/database/list/state-changes.ts similarity index 90% rename from src/database/list/state-changes.ts rename to src/compat/database/list/state-changes.ts index 192043e3b..10a4cdffa 100644 --- a/src/database/list/state-changes.ts +++ b/src/compat/database/list/state-changes.ts @@ -1,7 +1,7 @@ +import { SchedulerLike, merge } from 'rxjs'; import { ChildEvent, DatabaseQuery } from '../interfaces'; import { fromRef } from '../observable/fromRef'; import { validateEventsArray } from './utils'; -import { merge, SchedulerLike } from 'rxjs'; export function stateChanges(query: DatabaseQuery, events?: ChildEvent[], scheduler?: SchedulerLike) { events = validateEventsArray(events); diff --git a/src/database/list/utils.ts b/src/compat/database/list/utils.ts similarity index 100% rename from src/database/list/utils.ts rename to src/compat/database/list/utils.ts diff --git a/src/compat/database/ng-package.json b/src/compat/database/ng-package.json new file mode 100644 index 000000000..e5e5ccdfc --- /dev/null +++ b/src/compat/database/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/database/object/create-reference.ts b/src/compat/database/object/create-reference.ts similarity index 78% rename from src/database/object/create-reference.ts rename to src/compat/database/object/create-reference.ts index cede58f97..285f0e90c 100644 --- a/src/database/object/create-reference.ts +++ b/src/compat/database/object/create-reference.ts @@ -1,14 +1,16 @@ +import { Injector } from '@angular/core'; +import { pendingUntilEvent } from '@angular/core/rxjs-interop'; import { map } from 'rxjs/operators'; +import { AngularFireDatabase } from '../database'; import { AngularFireObject, DatabaseQuery } from '../interfaces'; import { createObjectSnapshotChanges } from './snapshot-changes'; -import { AngularFireDatabase } from '../database'; -export function createObjectReference(query: DatabaseQuery, afDatabase: AngularFireDatabase): AngularFireObject { +export function createObjectReference(query: DatabaseQuery, afDatabase: AngularFireDatabase, injector?: Injector): AngularFireObject { return { query, snapshotChanges() { return createObjectSnapshotChanges(query, afDatabase.schedulers.outsideAngular)().pipe( - afDatabase.keepUnstableUntilFirst + pendingUntilEvent(injector) ); }, update(data: Partial) { return query.ref.update(data as any) as Promise; }, @@ -17,7 +19,7 @@ export function createObjectReference(query: DatabaseQuery, afDatabase: valueChanges() { const snapshotChanges$ = createObjectSnapshotChanges(query, afDatabase.schedulers.outsideAngular)(); return snapshotChanges$.pipe( - afDatabase.keepUnstableUntilFirst, + pendingUntilEvent(injector), map(action => action.payload.exists() ? action.payload.val() as T : null) ); }, diff --git a/src/database/object/snapshot-changes.ts b/src/compat/database/object/snapshot-changes.ts similarity index 100% rename from src/database/object/snapshot-changes.ts rename to src/compat/database/object/snapshot-changes.ts index 66ae6ef3c..bff2f90c9 100644 --- a/src/database/object/snapshot-changes.ts +++ b/src/compat/database/object/snapshot-changes.ts @@ -1,6 +1,6 @@ import { Observable, SchedulerLike } from 'rxjs'; -import { fromRef } from '../observable/fromRef'; import { DatabaseQuery, SnapshotAction } from '../interfaces'; +import { fromRef } from '../observable/fromRef'; export function createObjectSnapshotChanges(query: DatabaseQuery, scheduler?: SchedulerLike) { return function snapshotChanges(): Observable> { diff --git a/src/database/observable/fromRef.spec.ts b/src/compat/database/observable/fromRef.spec.ts similarity index 86% rename from src/database/observable/fromRef.spec.ts rename to src/compat/database/observable/fromRef.spec.ts index e7a1f966e..c7c37455c 100644 --- a/src/database/observable/fromRef.spec.ts +++ b/src/compat/database/observable/fromRef.spec.ts @@ -1,16 +1,17 @@ -import { DatabaseReference } from '../interfaces'; -import { AngularFireModule, FirebaseApp, ÉĩZoneScheduler } from '@angular/fire'; -import { AngularFireDatabase, AngularFireDatabaseModule, fromRef } from '../public_api'; +/* eslint-disable @typescript-eslint/unbound-method */ import { TestBed } from '@angular/core/testing'; -import { COMMON_CONFIG } from '../../test-config'; +import { ÉĩZoneScheduler } from '@angular/fire'; +import { AngularFireModule } from '@angular/fire/compat'; +import { AngularFireDatabase, AngularFireDatabaseModule, USE_EMULATOR, fromRef } from '@angular/fire/compat/database'; +import firebase from 'firebase/compat/app'; import { take } from 'rxjs/operators'; import { TestScheduler } from 'rxjs/testing'; -import { rando } from '../../firestore/utils.spec'; +import { COMMON_CONFIG, databaseEmulatorPort } from '../../../../src/test-config'; +import { rando } from '../../../../src/utils'; describe('fromRef', () => { - let app: FirebaseApp; let db: AngularFireDatabase; - let ref: (path: string) => DatabaseReference; + let ref: (path: string) => firebase.database.Reference; let batch = {}; const items = [{ name: 'one' }, { name: 'two' }, { name: 'three' }].map(item => ({ key: rando(), ...item })); Object.keys(items).forEach((key) => { @@ -27,19 +28,14 @@ describe('fromRef', () => { AngularFireDatabaseModule ], providers: [ - { provide: URL, useValue: 'http://localhost:9000' } + { provide: USE_EMULATOR, useValue: ['localhost', databaseEmulatorPort] } ] }); - app = TestBed.inject(FirebaseApp); db = TestBed.inject(AngularFireDatabase); ref = (path: string) => db.database.ref(path); }); - afterEach(() => { - app.delete(); - }); - it('it should be async by default', (done) => { const itemRef = ref(rando()); itemRef.set(batch); @@ -69,11 +65,11 @@ describe('fromRef', () => { obs.subscribe(() => { expect(testScheduler.schedule).toHaveBeenCalled(); - done(); + // done(); }, err => { console.error(err); - expect(false).toEqual(true, 'Shouldnt error'); - done(); + expect(false).withContext('Shouldnt error').toEqual(true); + // done(); }, () => { expect(testScheduler.schedule).toHaveBeenCalled(); done(); @@ -119,8 +115,7 @@ describe('fromRef', () => { scheduler ); completeObservable.subscribe( - () => { - }, + () => undefined, () => fail('Should not error'), () => expect(Zone.current.name).toEqual('ExpectedZone') ); @@ -143,9 +138,7 @@ describe('fromRef', () => { const itemRef = ref(rando()); itemRef.set(batch); const obs = fromRef(itemRef, 'value', 'once'); - obs.subscribe(() => { - }, () => { - }, done); + obs.subscribe(() => undefined, () => undefined, done); }); it('it should listen and then unsubscribe', (done) => { @@ -166,7 +159,7 @@ describe('fromRef', () => { describe('events', () => { - it('should stream back a child_added event', async (done: any) => { + it('should stream back a child_added event', done => { const itemRef = ref(rando()); itemRef.set(batch); const obs = fromRef(itemRef, 'child_added'); @@ -184,7 +177,7 @@ describe('fromRef', () => { }); }); - it('should stream back a child_changed event', async (done: any) => { + it('should stream back a child_changed event', done => { const itemRef = ref(rando()); itemRef.set(batch); const obs = fromRef(itemRef, 'child_changed'); @@ -201,7 +194,7 @@ describe('fromRef', () => { itemRef.child(key).update({ name }); }); - it('should stream back a child_removed event', async (done: any) => { + it('should stream back a child_removed event', done => { const itemRef = ref(rando()); itemRef.set(batch); const obs = fromRef(itemRef, 'child_removed'); @@ -218,7 +211,7 @@ describe('fromRef', () => { itemRef.child(key).remove(); }); - it('should stream back a child_moved event', async (done: any) => { + it('should stream back a child_moved event', done => { const itemRef = ref(rando()); itemRef.set(batch); const obs = fromRef(itemRef, 'child_moved'); @@ -232,8 +225,7 @@ describe('fromRef', () => { sub.unsubscribe(); done(); }); - itemRef.child(key).setPriority(-100, () => { - }); + itemRef.child(key).setPriority(-100, () => undefined); }); it('should stream back a value event', (done: any) => { diff --git a/src/database/observable/fromRef.ts b/src/compat/database/observable/fromRef.ts similarity index 91% rename from src/database/observable/fromRef.ts rename to src/compat/database/observable/fromRef.ts index d08283235..81845e86a 100644 --- a/src/database/observable/fromRef.ts +++ b/src/compat/database/observable/fromRef.ts @@ -1,6 +1,6 @@ -import { AngularFireAction, DatabaseQuery, DatabaseSnapshot, ListenEvent } from '../interfaces'; -import { asyncScheduler, Observable, SchedulerLike } from 'rxjs'; +import { Observable, SchedulerLike, asyncScheduler } from 'rxjs'; import { map, share } from 'rxjs/operators'; +import { AngularFireAction, DatabaseQuery, DatabaseSnapshot, ListenEvent } from '../interfaces'; interface SnapshotPrevKey { snapshot: DatabaseSnapshot; @@ -20,7 +20,7 @@ export function fromRef(ref: DatabaseQuery, scheduler: SchedulerLike = asyncScheduler ): Observable>> { return new Observable>(subscriber => { - let fn: any | null = null; + let fn: any = null; fn = ref[listenType](event, (snapshot, prevKey) => { scheduler.schedule(() => { subscriber.next({ snapshot, prevKey }); @@ -42,6 +42,7 @@ export function fromRef(ref: DatabaseQuery, }; } else { return { + // eslint-disable-next-line @typescript-eslint/no-empty-function unsubscribe() { } }; diff --git a/src/compat/database/package.json b/src/compat/database/package.json new file mode 100644 index 000000000..8dce418a5 --- /dev/null +++ b/src/compat/database/package.json @@ -0,0 +1,3 @@ +{ + "$schema": "../../node_modules/ng-packagr/package.schema.json" +} diff --git a/src/compat/database/public_api.ts b/src/compat/database/public_api.ts new file mode 100644 index 000000000..b1f0ab756 --- /dev/null +++ b/src/compat/database/public_api.ts @@ -0,0 +1,8 @@ +export * from './database'; +export * from './list/changes'; +export * from './list/create-reference'; +export * from './list/snapshot-changes'; +export * from './list/state-changes'; +export * from './list/audit-trail'; +export * from './observable/fromRef'; +export * from './database.module'; diff --git a/src/database/utils.spec.ts b/src/compat/database/utils.spec.ts similarity index 95% rename from src/database/utils.spec.ts rename to src/compat/database/utils.spec.ts index b437b0700..7363ddc53 100644 --- a/src/database/utils.spec.ts +++ b/src/compat/database/utils.spec.ts @@ -10,7 +10,7 @@ describe('utils', () => { const bool = true; const nul = null; const obj = {}; - const fn = () => { }; + const fn = () => undefined; const undef = undefined; expect(utils.isString(str)).toBe(true); expect(utils.isString(notStr)).toBe(false); diff --git a/src/database/utils.ts b/src/compat/database/utils.ts similarity index 89% rename from src/database/utils.ts rename to src/compat/database/utils.ts index 8811bdbfa..8a648bb00 100644 --- a/src/database/utils.ts +++ b/src/compat/database/utils.ts @@ -1,5 +1,5 @@ +import firebase from 'firebase/compat/app'; import { DatabaseReference, FirebaseOperation, FirebaseOperationCases, PathReference } from './interfaces'; -import { database } from 'firebase/app'; export function isString(value: any): boolean { return typeof value === 'string'; @@ -23,7 +23,7 @@ export function isFirebaseRef(value: any): boolean { * @param database - Firebase Database * @param pathRef - Database path, relative or absolute */ -export function getRef(database: database.Database, pathRef: PathReference): DatabaseReference { +export function getRef(database: firebase.database.Database, pathRef: PathReference): DatabaseReference { // if a db ref was passed in, just return it return isFirebaseRef(pathRef) ? pathRef as DatabaseReference : database.ref(pathRef as string); diff --git a/src/compat/firebase.app.module.ts b/src/compat/firebase.app.module.ts new file mode 100644 index 000000000..6e54b0fd4 --- /dev/null +++ b/src/compat/firebase.app.module.ts @@ -0,0 +1,68 @@ +import { + Inject, InjectionToken, ModuleWithProviders, VERSION as NG_VERSION, NgModule, NgZone, Optional, PLATFORM_ID, isDevMode +} from '@angular/core'; +import { VERSION } from '@angular/fire'; +import { FirebaseAppSettings, FirebaseOptions } from 'firebase/app'; +import firebase from 'firebase/compat/app'; +import { FirebaseApp } from './firebase.app'; + +export const FIREBASE_OPTIONS = new InjectionToken('angularfire2.app.options'); +export const FIREBASE_APP_NAME = new InjectionToken('angularfire2.app.name'); + + +export function ÉĩfirebaseAppFactory(options: FirebaseOptions, zone: NgZone, nameOrConfig?: string | FirebaseAppSettings | null) { + const name = typeof nameOrConfig === 'string' && nameOrConfig || '[DEFAULT]'; + const config = typeof nameOrConfig === 'object' && nameOrConfig || {}; + config.name = config.name || name; + // Added any due to some inconsistency between @firebase/app and firebase types + const existingApp = firebase.apps.find(app => app && app.name === config.name); + // We support FirebaseConfig, initializeApp's public type only accepts string; need to cast as any + // Could be solved with https://github.com/firebase/firebase-js-sdk/pull/1206 + const app = (existingApp || zone.runOutsideAngular(() => firebase.initializeApp(options, config as any))); + try { + if (JSON.stringify(options) !== JSON.stringify(app.options)) { + const hmr = !!(module as any).hot; + log('error', `${app.name} Firebase App already initialized with different options${hmr ? ', you may need to reload as Firebase is not HMR aware.' : '.'}`); + } + } catch (_) { /* empty */ } + return new FirebaseApp(app); +} + +const log = (level: 'log' | 'error' | 'info' | 'warn', ...args: any) => { + if (isDevMode() && typeof console !== 'undefined') { + // eslint-disable-next-line no-console + console[level](...args); + } +}; + +const FIREBASE_APP_PROVIDER = { + provide: FirebaseApp, + useFactory: ÉĩfirebaseAppFactory, + deps: [ + FIREBASE_OPTIONS, + NgZone, + [new Optional(), FIREBASE_APP_NAME] + ] +}; + +@NgModule({ + providers: [FIREBASE_APP_PROVIDER] +}) +export class AngularFireModule { + static initializeApp(options: FirebaseOptions, nameOrConfig?: string | FirebaseAppSettings): ModuleWithProviders { + return { + ngModule: AngularFireModule, + providers: [ + { provide: FIREBASE_OPTIONS, useValue: options }, + { provide: FIREBASE_APP_NAME, useValue: nameOrConfig } + ] + }; + } + + constructor(@Inject(PLATFORM_ID) platformId: object) { + firebase.registerVersion('angularfire', VERSION.full, 'core'); + firebase.registerVersion('angularfire', VERSION.full, 'app-compat'); + // eslint-disable-next-line @typescript-eslint/no-base-to-string + firebase.registerVersion('angular', NG_VERSION.full, platformId.toString()); + } +} diff --git a/src/compat/firebase.app.ts b/src/compat/firebase.app.ts new file mode 100644 index 000000000..cf2cf6b1e --- /dev/null +++ b/src/compat/firebase.app.ts @@ -0,0 +1,10 @@ +import firebase from 'firebase/compat/app'; + + +export interface FirebaseApp extends firebase.app.App {} + +export class FirebaseApp { + constructor(app: firebase.app.App) { + return app; + } +} diff --git a/src/compat/firestore/collection-group/collection-group.spec.ts b/src/compat/firestore/collection-group/collection-group.spec.ts new file mode 100644 index 000000000..8b6f1ac43 --- /dev/null +++ b/src/compat/firestore/collection-group/collection-group.spec.ts @@ -0,0 +1,527 @@ +import { TestBed } from '@angular/core/testing'; +import { AngularFireModule } from '@angular/fire/compat'; +import { AngularFirestore, AngularFirestoreCollectionGroup , AngularFirestoreModule, Query, QueryGroupFn, USE_EMULATOR } from '@angular/fire/compat/firestore'; +import { BehaviorSubject } from 'rxjs'; +import { skip, switchMap, take } from 'rxjs/operators'; +import { COMMON_CONFIG, firestoreEmulatorPort } from '../../../test-config'; +import { rando } from '../../../utils'; +import { + FAKE_STOCK_DATA, + Stock, + createRandomStocks, + delayAdd, + delayDelete, + delayUpdate, + randomName +} from '../utils.spec'; +import 'firebase/compat/firestore'; + +async function collectionHarness(afs: AngularFirestore, items: number, queryGroupFn?: QueryGroupFn) { + const randomCollectionName = randomName(afs.firestore); + const ref = TestBed.runInInjectionContext(() => afs.firestore.collection(`${randomCollectionName}`)); + const firestore = afs.firestore; + const collectionGroup = TestBed.runInInjectionContext(() => firestore.collectionGroup(randomCollectionName)) as Query; + const queryFn = queryGroupFn || (ref => ref); + const stocks = new AngularFirestoreCollectionGroup(queryFn(collectionGroup), afs); + const names = await TestBed.runInInjectionContext(() => createRandomStocks(afs.firestore, ref, items)); + return { randomCollectionName, ref, stocks, names }; +} + +describe('AngularFirestoreCollectionGroup', () => { + let afs: AngularFirestore; + + beforeEach(() => { + pending("These are pretty broken, investigate."); + + TestBed.configureTestingModule({ + imports: [ + AngularFireModule.initializeApp(COMMON_CONFIG, rando()), + AngularFirestoreModule + ], + providers: [ + { provide: USE_EMULATOR, useValue: ['localhost', firestoreEmulatorPort] } + ] + }); + + afs = TestBed.inject(AngularFirestore); + }); + + describe('valueChanges()', () => { + + it('should get unwrapped snapshot', done => { + (async () => { + const ITEMS = 4; + const { stocks } = await collectionHarness(afs, ITEMS); + + const sub = TestBed.runInInjectionContext(() => stocks.valueChanges()).subscribe(data => { + // unsub immediately as we will be deleting data at the bottom + // and that will trigger another subscribe callback and fail + // the test + sub.unsubscribe(); + // We added four things. This should be four. + // This could not be four if the batch failed or + // if the collection state is altered during a test run + expect(data.length).toEqual(ITEMS); + data.forEach(stock => { + // We used the same piece of data so they should all equal + expect(stock).toEqual(FAKE_STOCK_DATA); + }); + done(); + }); + })(); + }); + + it('should handle multiple subscriptions (hot)', done => { + (async () => { + const ITEMS = 4; + const { stocks } = await collectionHarness(afs, ITEMS); + const changes = TestBed.runInInjectionContext(() => stocks.valueChanges()); + const sub = changes.subscribe(() => undefined); + sub.add( + changes.pipe(take(1)).subscribe(data => { + expect(data.length).toEqual(ITEMS); + sub.unsubscribe(); + done(); + }) + ); + })(); + }); + + it('should handle multiple subscriptions (warm)', done => { + (async () => { + const ITEMS = 4; + const { stocks } = await collectionHarness(afs, ITEMS); + const changes = TestBed.runInInjectionContext(() => stocks.valueChanges()); + changes.pipe(take(1)).subscribe(() => undefined).add(() => { + changes.pipe(take(1)).subscribe(data => { + expect(data.length).toEqual(ITEMS); + done(); + }) + }); + })(); + }); + + it('should handle dynamic queries that return empty sets', done => { + (async () => { + const ITEMS = 10; + let count = 0; + + const pricefilter$ = new BehaviorSubject(null); + const randomCollectionName = randomName(afs.firestore); + const ref = TestBed.runInInjectionContext(() => afs.firestore.collection(`${randomCollectionName}`)); + await createRandomStocks(afs.firestore, ref, ITEMS); + const sub = pricefilter$.pipe(switchMap(price => { + return TestBed.runInInjectionContext(() => afs.collection(randomCollectionName, ref => price ? ref.where('price', '==', price) : ref).valueChanges()); + })).subscribe(data => { + count = count + 1; + // the first time should all be 'added' + if (count === 1) { + expect(data.length).toEqual(ITEMS); + pricefilter$.next(-1); + } + // on the second round, we should have filtered out everything + if (count === 2) { + expect(data.length).toEqual(0); + sub.unsubscribe(); + done(); + } + }); + })(); + }); + + it('should return the document\'s id along with the data if the idField option is provided.', done => { + (async () => { + const ITEMS = 4; + const DOC_ID = 'docId'; + const { stocks } = await collectionHarness(afs, ITEMS); + + const sub = TestBed.runInInjectionContext(() => stocks.valueChanges({idField: DOC_ID})).subscribe(data => { + const allDocumentsHaveId = data.every(d => d.docId !== undefined); + + expect(allDocumentsHaveId).toBe(true); + sub.unsubscribe(); + done(); + }); + })(); + }); + + }); + + describe('snapshotChanges()', () => { + + it('should listen to all snapshotChanges() by default', done => { + (async () => { + const ITEMS = 10; + let count = 0; + const { ref, stocks, names } = await collectionHarness(afs, ITEMS); + const sub = TestBed.runInInjectionContext(() => stocks.snapshotChanges()).subscribe(data => { + count = count + 1; + // the first time should all be 'added' + if (count === 1) { + // make an update + ref.doc(names[0]).update({ price: 2 }); + } + // on the second round, make sure the array is still the same + // length but the updated item is now modified + if (count === 2) { + expect(data.length).toEqual(ITEMS); + const change = data.find(x => x.payload.doc.id === names[0]); + expect(change.type).toEqual('modified'); + sub.unsubscribe(); + done(); + } + }); + })(); + }); + + it('should handle multiple subscriptions (hot)', done => { + (async () => { + const ITEMS = 4; + const { stocks } = await collectionHarness(afs, ITEMS); + const changes = TestBed.runInInjectionContext(() => stocks.snapshotChanges()); + const sub = changes.subscribe(() => undefined); + sub.add( + changes.pipe(take(1)).subscribe(data => { + expect(data.length).toEqual(ITEMS); + sub.unsubscribe(); + done(); + }) + ); + })(); + }); + + it('should handle multiple subscriptions (warm)', done => { + (async () => { + const ITEMS = 4; + const { stocks } = await collectionHarness(afs, ITEMS); + const changes = TestBed.runInInjectionContext(() => stocks.snapshotChanges()); + changes.pipe(take(1)).subscribe(() => undefined).add(() => { + changes.pipe(take(1)).subscribe(data => { + expect(data.length).toEqual(ITEMS); + done(); + }); + }); + })(); + }); + + it('should update order on queries', done => { + (async () => { + const ITEMS = 10; + let count = 0; + let firstIndex = 0; + const { ref, stocks, names } = + await collectionHarness(afs, ITEMS, ref => ref.orderBy('price', 'desc')); + const sub = TestBed.runInInjectionContext(() => stocks.snapshotChanges()).subscribe(data => { + count = count + 1; + // the first time should all be 'added' + if (count === 1) { + // make an update + firstIndex = data.find(d => d.payload.doc.id === names[0]).payload.newIndex; + ref.doc(names[0]).update({ price: 2 }); + } + // on the second round, make sure the array is still the same + // length but the updated item is now modified + if (count === 2) { + expect(data.length).toEqual(ITEMS); + const change = data.find(x => x.payload.doc.id === names[0]); + expect(change.type).toEqual('modified'); + expect(change.payload.oldIndex).toEqual(firstIndex); + sub.unsubscribe(); + done(); + } + }); + })(); + }); + + it('should be able to filter snapshotChanges() types - modified', done => { + pending("TODO(jamesdaniels) find out why this is flaking with SDK v11"); + (async () => { + const ITEMS = 10; + const { ref, stocks, names } = await collectionHarness(afs, ITEMS); + + const sub = TestBed.runInInjectionContext(() => stocks.snapshotChanges(['modified'])).pipe(skip(1)).subscribe(data => { + sub.unsubscribe(); + const change = data.find(x => x.payload.doc.id === names[0]); + expect(data.length).toEqual(1); + expect(change.payload.doc.data().price).toEqual(2); + expect(change.type).toEqual('modified'); + done(); + }); + + delayUpdate(ref, names[0], { price: 2 }); + })(); + }); + + it('should be able to filter snapshotChanges() types - added', done => { + (async () => { + const ITEMS = 10; + const harness = await collectionHarness(afs, ITEMS); + const { randomCollectionName, ref, stocks } = harness; + let { names } = harness; + const nextId = ref.doc('a').id; + + const sub = TestBed.runInInjectionContext(() => stocks.snapshotChanges(['added'])).pipe(skip(1)).subscribe(data => { + sub.unsubscribe(); + const change = data.find(x => x.payload.doc.id === nextId); + expect(data.length).toEqual(ITEMS + 1); + expect(change.payload.doc.data().price).toEqual(2); + expect(change.type).toEqual('added'); + done(); + }); + + + names = names.concat([nextId]); + // TODO these two add tests are the only one really testing collection-group queries + // should flex more, maybe split the stocks between more than one collection + delayAdd(ref.doc(names[0]).collection(randomCollectionName), nextId, { price: 2 }); + })(); + }); + + it('should be able to filter snapshotChanges() types - added w/same id', done => { + (async () => { + const ITEMS = 10; + const { randomCollectionName, ref, stocks, names } = await collectionHarness(afs, ITEMS); + + const sub = TestBed.runInInjectionContext(() => stocks.snapshotChanges(['added'])).pipe(skip(1)).subscribe(data => { + sub.unsubscribe(); + const change = data.filter(x => x.payload.doc.id === names[0])[1]; + expect(data.length).toEqual(ITEMS + 1); + expect(change.payload.doc.data().price).toEqual(3); + expect(change.type).toEqual('added'); + done(); + }); + + delayAdd(ref.doc(names[0]).collection(randomCollectionName), names[0], { price: 3 }); + })(); + }); + + /* TODO(jamesdaniels): revisit this test with metadata changes, need to do some additional skips + it('should be able to filter snapshotChanges() types - added/modified', done => { + (async () => { + const ITEMS = 10; + + const harness = await collectionHarness(afs, ITEMS); + const { ref, stocks } = harness; + let { names } = harness; + + const nextId = ref.doc('a').id; + let count = 0; + + stocks.snapshotChanges(['added', 'modified']).pipe(skip(1), take(2)).subscribe(data => { + count += 1; + if (count === 1) { + const change = data.filter(x => x.payload.doc.id === nextId)[0]; + expect(data.length).toEqual(ITEMS + 1); + expect(change.payload.doc.data().price).toEqual(2); + expect(change.type).toEqual('added'); + delayUpdate(ref, names[0], { price: 2 }); + } + if (count === 2) { + const change = data.filter(x => x.payload.doc.id === names[0])[0]; + expect(data.length).toEqual(ITEMS + 1); + expect(change.payload.doc.data().price).toEqual(2); + expect(change.type).toEqual('modified'); + } + }).add(() => { + deleteThemAll(names, ref) + .then(() => { + done() + }) + .catch(() => { + done.fail() + }); + }); + + names = names.concat([nextId]); + delayAdd(ref, nextId, { price: 2 }); + })(); + }); + */ + + it('should be able to filter snapshotChanges() types - removed', done => { + (async () => { + + const ITEMS = 10; + const { ref, stocks, names } = await collectionHarness(afs, ITEMS); + + const sub = TestBed.runInInjectionContext(() => stocks.snapshotChanges(['added', 'removed'])).pipe(skip(1)).subscribe(data => { + sub.unsubscribe(); + const change = data.filter(x => x.payload.doc.id === names[0]); + expect(data.length).toEqual(ITEMS - 1); + expect(change.length).toEqual(0); + done(); + }); + + delayDelete(ref, names[0], 400); + })(); + }); + + }); + + describe('stateChanges()', () => { + + it('should get stateChanges() updates', done => { + (async () => { + const ITEMS = 10; + const { stocks } = await collectionHarness(afs, ITEMS); + + const sub = TestBed.runInInjectionContext(() => stocks.stateChanges()).subscribe(data => { + // unsub immediately as we will be deleting data at the bottom + // and that will trigger another subscribe callback and fail + // the test + sub.unsubscribe(); + // We added ten things. This should be ten. + // This could not be ten if the batch failed or + // if the collection state is altered during a test run + expect(data.length).toEqual(ITEMS); + data.forEach(action => { + // We used the same piece of data so they should all equal + expect(action.payload.doc.data()).toEqual(FAKE_STOCK_DATA); + }); + done(); + }); + })(); + }); + + it('should listen to all stateChanges() by default', done => { + (async () => { + const ITEMS = 10; + let count = 0; + const { ref, stocks, names } = await collectionHarness(afs, ITEMS); + TestBed.runInInjectionContext(() => stocks.stateChanges()).subscribe(data => { + count = count + 1; + if (count === 1) { + ref.doc(names[0]).update({ price: 2 }); + } + if (count === 2) { + expect(data.length).toEqual(1); + expect(data[0].type).toEqual('modified'); + done(); + } + }); + })(); + }); + + it('should handle multiple subscriptions (hot)', done => { + (async () => { + const ITEMS = 4; + const { stocks } = await collectionHarness(afs, ITEMS); + const changes = TestBed.runInInjectionContext(() => stocks.stateChanges()); + const sub = changes.subscribe(() => undefined); + sub.add( + changes.pipe(take(1)).subscribe(data => { + expect(data.length).toEqual(ITEMS); + sub.unsubscribe(); + done(); + }) + ); + })(); + }); + + it('should handle multiple subscriptions (warm)', done => { + (async () => { + const ITEMS = 4; + const { stocks } = await collectionHarness(afs, ITEMS); + const changes = TestBed.runInInjectionContext(() => stocks.stateChanges()); + changes.pipe(take(1)).subscribe(() => undefined).add(() => { + changes.pipe(take(1)).subscribe(data => { + expect(data.length).toEqual(ITEMS); + done(); + }); + }); + })(); + }); + + it('should be able to filter stateChanges() types - modified', done => { + pending("TODO(jamesdaniels) find out why this is flaking with SDK v11"); + (async () => { + const ITEMS = 10; + const { ref, stocks, names } = await collectionHarness(afs, ITEMS); + + const sub = TestBed.runInInjectionContext(() => stocks.stateChanges(['modified'])).subscribe(data => { + sub.unsubscribe(); + expect(data.length).toEqual(1); + expect(data[0].payload.doc.data().price).toEqual(2); + expect(data[0].type).toEqual('modified'); + done(); + }); + + delayUpdate(ref, names[0], { price: 2 }); + })(); + }); + + it('should be able to filter stateChanges() types - added', done => { + (async () => { + const ITEMS = 10; + + const { ref, stocks } = await collectionHarness(afs, ITEMS); + + const sub = TestBed.runInInjectionContext(() => stocks.stateChanges(['added'])).pipe(skip(1)).subscribe(data => { + sub.unsubscribe(); + expect(data.length).toEqual(1); + expect(data[0].payload.doc.data().price).toEqual(2); + expect(data[0].type).toEqual('added'); + done(); + }); + + const nextId = ref.doc('a').id; + delayAdd(ref, nextId, { price: 2 }); + })(); + }); + + it('should be able to filter stateChanges() types - removed', done => { + (async () => { + const ITEMS = 10; + const { ref, stocks, names } = await collectionHarness(afs, ITEMS); + + const sub = TestBed.runInInjectionContext(() => stocks.stateChanges(['removed'])).subscribe(data => { + sub.unsubscribe(); + expect(data.length).toEqual(1); + expect(data[0].type).toEqual('removed'); + done(); + }); + + delayDelete(ref, names[0], 400); + })(); + }); + }); + + describe('auditTrail()', () => { + it('should listen to all events for auditTrail() by default', done => { + (async () => { + const ITEMS = 10; + let count = 0; + const { ref, stocks, names } = await collectionHarness(afs, ITEMS); + const sub = TestBed.runInInjectionContext(() => stocks.auditTrail()).subscribe(data => { + count = count + 1; + if (count === 1) { + ref.doc(names[0]).update({ price: 2 }); + } + if (count === 2) { + sub.unsubscribe(); + expect(data.length).toEqual(ITEMS + 1); + expect(data[data.length - 1].type).toEqual('modified'); + done(); + } + }); + })(); + }); + + it('should be able to filter auditTrail() types - removed', done => { + (async () => { + const ITEMS = 10; + const { ref, stocks, names } = await collectionHarness(afs, ITEMS); + + const sub = TestBed.runInInjectionContext(() => stocks.auditTrail(['removed'])).subscribe(data => { + sub.unsubscribe(); + expect(data.length).toEqual(1); + expect(data[0].type).toEqual('removed'); + done(); + }); + + delayDelete(ref, names[0], 400); + })(); + }); + }); + +}); diff --git a/src/firestore/collection-group/collection-group.ts b/src/compat/firestore/collection-group/collection-group.ts similarity index 67% rename from src/firestore/collection-group/collection-group.ts rename to src/compat/firestore/collection-group/collection-group.ts index 1c3aff9fd..58806d1f4 100644 --- a/src/firestore/collection-group/collection-group.ts +++ b/src/compat/firestore/collection-group/collection-group.ts @@ -1,12 +1,13 @@ -import { from, Observable } from 'rxjs'; -import { fromCollectionRef } from '../observable/fromRef'; -import { filter, map, observeOn, scan } from 'rxjs/operators'; -import { firestore } from 'firebase/app'; - -import { DocumentChangeAction, DocumentChangeType, DocumentData, Query } from '../interfaces'; -import { validateEventsArray } from '../collection/collection'; +import { EnvironmentInjector, inject } from '@angular/core'; +import { pendingUntilEvent } from '@angular/core/rxjs-interop'; +import firebase from 'firebase/compat/app'; +import { Observable, from } from 'rxjs'; +import { filter, map, scan } from 'rxjs/operators'; import { docChanges, sortedChanges } from '../collection/changes'; +import { validateEventsArray } from '../collection/collection'; import { AngularFirestore } from '../firestore'; +import { DocumentChangeAction, DocumentChangeType, DocumentData, Query } from '../interfaces'; +import { fromCollectionRef } from '../observable/fromRef'; /** * AngularFirestoreCollectionGroup service @@ -26,13 +27,15 @@ import { AngularFirestore } from '../firestore'; * // Subscribe to changes as snapshots. This provides you data updates as well as delta updates. * fakeStock.valueChanges().subscribe(value => console.log(value)); */ -export class AngularFirestoreCollectionGroup { +export class AngularFirestoreCollectionGroup { + private readonly injector = inject(EnvironmentInjector); + /** * The constructor takes in a CollectionGroupQuery to provide wrapper methods * for data operations and data streaming. */ constructor( - private readonly query: Query, + private readonly query: Query, private readonly afs: AngularFirestore) { } /** @@ -43,14 +46,14 @@ export class AngularFirestoreCollectionGroup { stateChanges(events?: DocumentChangeType[]): Observable[]> { if (!events || events.length === 0) { return docChanges(this.query, this.afs.schedulers.outsideAngular).pipe( - this.afs.keepUnstableUntilFirst + pendingUntilEvent(this.injector) ); } return docChanges(this.query, this.afs.schedulers.outsideAngular) .pipe( - map(actions => actions.filter(change => events.indexOf(change.type) > -1)), + map(actions => actions.filter(change => events.includes(change.type))), filter(changes => changes.length > 0), - this.afs.keepUnstableUntilFirst + pendingUntilEvent(this.injector) ); } @@ -70,28 +73,44 @@ export class AngularFirestoreCollectionGroup { const validatedEvents = validateEventsArray(events); const scheduledSortedChanges$ = sortedChanges(this.query, validatedEvents, this.afs.schedulers.outsideAngular); return scheduledSortedChanges$.pipe( - this.afs.keepUnstableUntilFirst + pendingUntilEvent(this.injector) ); } /** * Listen to all documents in the collection and its possible query as an Observable. + * + * If the `idField` option is provided, document IDs are included and mapped to the + * provided `idField` property name. */ - valueChanges(): Observable { + valueChanges(): Observable; + // eslint-disable-next-line no-empty-pattern + valueChanges({}): Observable; + valueChanges(options: {idField: K}): Observable<(T & Record)[]>; + valueChanges(options: {idField?: K} = {}): Observable { const fromCollectionRefScheduled$ = fromCollectionRef(this.query, this.afs.schedulers.outsideAngular); return fromCollectionRefScheduled$ .pipe( - map(actions => actions.payload.docs.map(a => a.data())), - this.afs.keepUnstableUntilFirst + map(actions => actions.payload.docs.map(a => { + if (options.idField) { + return { + [options.idField]: a.id, + ...a.data() + } as T & Record; + } else { + return a.data(); + } + })), + pendingUntilEvent(this.injector) ); } /** * Retrieve the results of the query once. */ - get(options?: firestore.GetOptions) { + get(options?: firebase.firestore.GetOptions) { return from(this.query.get(options)).pipe( - observeOn(this.afs.schedulers.insideAngular) + pendingUntilEvent(this.injector) ); } diff --git a/src/compat/firestore/collection/changes.ts b/src/compat/firestore/collection/changes.ts new file mode 100644 index 000000000..4d2415c7d --- /dev/null +++ b/src/compat/firestore/collection/changes.ts @@ -0,0 +1,128 @@ +import firebase from 'firebase/compat/app'; +import { Observable, SchedulerLike } from 'rxjs'; +import { distinctUntilChanged, map, pairwise, scan, startWith } from 'rxjs/operators'; +import { Action, DocumentChange, DocumentChangeAction, DocumentChangeType, Query, QuerySnapshot } from '../interfaces'; +import { fromCollectionRef } from '../observable/fromRef'; + + +type ActionTupe = [Action>, Action>] +/** + * Return a stream of document changes on a query. These results are not in sort order but in + * order of occurence. + */ +export function docChanges(query: Query, scheduler?: SchedulerLike): Observable[]> { + return fromCollectionRef(query, scheduler) + .pipe( + startWith>, undefined>(undefined), + pairwise(), + map((actionTuple: ActionTupe) => { + const [priorAction, action] = actionTuple; + const docChanges = action.payload.docChanges(); + const actions = docChanges.map(change => ({ type: change.type, payload: change })); + // the metadata has changed from the prior emission + if (priorAction && JSON.stringify(priorAction.payload.metadata) !== JSON.stringify(action.payload.metadata)) { + // go through all the docs in payload and figure out which ones changed + action.payload.docs.forEach((currentDoc, currentIndex) => { + const docChange = docChanges.find(d => d.doc.ref.isEqual(currentDoc.ref)); + const priorDoc = priorAction?.payload.docs.find(d => d.ref.isEqual(currentDoc.ref)); + if (docChange && JSON.stringify(docChange.doc.metadata) === JSON.stringify(currentDoc.metadata) || + !docChange && priorDoc && JSON.stringify(priorDoc.metadata) === JSON.stringify(currentDoc.metadata)) { + // document doesn't appear to have changed, don't log another action + } else { + // since the actions are processed in order just push onto the array + actions.push({ + type: 'modified', + payload: { + oldIndex: currentIndex, + newIndex: currentIndex, + type: 'modified', + doc: currentDoc + } + }); + } + }); + } + return actions as DocumentChangeAction[]; + }), + ); +} + +/** + * Return a stream of document changes on a query. These results are in sort order. + */ +export function sortedChanges( + query: Query, + events: DocumentChangeType[], + scheduler?: SchedulerLike): Observable[]> { + return docChanges(query, scheduler) + .pipe( + scan((current, changes) => combineChanges(current, changes.map(it => it.payload), events), []), + distinctUntilChanged(), // cut down on unneed change cycles + map(changes => changes.map(c => ({ type: c.type, payload: c } as DocumentChangeAction)))); +} + +/** + * Combines the total result set from the current set of changes from an incoming set + * of changes. + */ +export function combineChanges(current: DocumentChange[], changes: DocumentChange[], events: DocumentChangeType[]) { + changes.forEach(change => { + // skip unwanted change types + if (events.includes(change.type)) { + current = combineChange(current, change); + } + }); + return current; +} + +/** + * Splice arguments on top of a sliced array, to break top-level === + * this is useful for change-detection + */ +function sliceAndSplice( + original: T[], + start: number, + deleteCount: number, + ...args: T[] +): T[] { + const returnArray = original.slice(); + returnArray.splice(start, deleteCount, ...args); + return returnArray; +} + +/** + * Creates a new sorted array from a new change. + * Build our own because we allow filtering of action types ('added', 'removed', 'modified') before scanning + * and so we have greater control over change detection (by breaking ===) + */ +export function combineChange(combined: DocumentChange[], change: DocumentChange): DocumentChange[] { + switch (change.type) { + case 'added': + if (combined[change.newIndex]?.doc.ref.isEqual(change.doc.ref)) { + // Not sure why the duplicates are getting fired + } else { + return sliceAndSplice(combined, change.newIndex, 0, change); + } + break; + case 'modified': + if (combined[change.oldIndex] == null || combined[change.oldIndex].doc.ref.isEqual(change.doc.ref)) { + // When an item changes position we first remove it + // and then add it's new position + if (change.oldIndex !== change.newIndex) { + const copiedArray = combined.slice(); + copiedArray.splice(change.oldIndex, 1); + copiedArray.splice(change.newIndex, 0, change); + return copiedArray; + } else { + return sliceAndSplice(combined, change.newIndex, 1, change); + } + } + break; + case 'removed': + if (combined[change.oldIndex]?.doc.ref.isEqual(change.doc.ref)) { + return sliceAndSplice(combined, change.oldIndex, 1); + } + break; + } + return combined; +} diff --git a/src/compat/firestore/collection/collection.spec.ts b/src/compat/firestore/collection/collection.spec.ts new file mode 100644 index 000000000..b96bc3ba6 --- /dev/null +++ b/src/compat/firestore/collection/collection.spec.ts @@ -0,0 +1,520 @@ +import { TestBed } from '@angular/core/testing'; +import { AngularFireModule } from '@angular/fire/compat'; +import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreModule, CollectionReference, QueryFn, USE_EMULATOR } from '@angular/fire/compat/firestore'; +import { BehaviorSubject } from 'rxjs'; +import { skip, switchMap, take } from 'rxjs/operators'; +import { COMMON_CONFIG, firestoreEmulatorPort } from '../../../test-config'; +import { rando } from '../../../utils'; +import { + FAKE_STOCK_DATA, + Stock, + createRandomStocks, + delayAdd, + delayDelete, + delayUpdate, + randomName +} from '../utils.spec'; + +import 'firebase/compat/firestore'; + +async function collectionHarness(afs: AngularFirestore, items: number, queryFn?: QueryFn) { + const randomCollectionName = randomName(afs.firestore); + const ref = afs.firestore.collection(`${randomCollectionName}`) as CollectionReference; + if (!queryFn) { + queryFn = (ref) => ref; + } + const stocks = new AngularFirestoreCollection(ref, queryFn(ref), afs); + const names = await createRandomStocks(afs.firestore, ref, items); + return { randomCollectionName, ref, stocks, names }; +} + +describe('AngularFirestoreCollection', () => { + + + let afs: AngularFirestore; + + beforeEach(() => { + pending("These are pretty broken, investigate."); + + TestBed.configureTestingModule({ + imports: [ + AngularFireModule.initializeApp(COMMON_CONFIG, rando()), + AngularFirestoreModule + ], + providers: [ + { provide: USE_EMULATOR, useValue: ['localhost', firestoreEmulatorPort] } + ] + }); + + afs = TestBed.inject(AngularFirestore); + }); + + describe('valueChanges()', () => { + + it('should get unwrapped snapshot', done => { + (async () => { + const ITEMS = 4; + const { stocks } = await collectionHarness(afs, ITEMS); + + const sub = TestBed.runInInjectionContext(() => stocks.valueChanges()).subscribe(data => { + // unsub immediately as we will be deleting data at the bottom + // and that will trigger another subscribe callback and fail + // the test + sub.unsubscribe(); + // We added four things. This should be four. + // This could not be four if the batch failed or + // if the collection state is altered during a test run + expect(data.length).toEqual(ITEMS); + data.forEach(stock => { + // We used the same piece of data so they should all equal + expect(stock).toEqual(FAKE_STOCK_DATA); + }); + done(); + }); + })(); + }); + + /* FLAKE? timing out + it('should optionally map the doc ID to the emitted data object', done => { + const ITEMS = 1; + const { ref, stocks, names } = await collectionHarness(afs, ITEMS); + const idField = 'myCustomID'; + const sub = stocks.valueChanges({idField}).subscribe(data => { + sub.unsubscribe(); + const stock = data[0]; + expect(stock[idField]).toBeDefined(); + expect(stock).toEqual(jasmine.objectContaining(FAKE_STOCK_DATA)); + deleteThemAll(names, ref).then(done).catch(fail); + }) + });*/ + + it('should handle multiple subscriptions (hot)', done => { + (async () => { + const ITEMS = 4; + const { stocks } = await collectionHarness(afs, ITEMS); + const changes = TestBed.runInInjectionContext(() => stocks.valueChanges()); + const sub = changes.subscribe(() => undefined); + sub.add( + changes.pipe(take(1)).subscribe(data => { + expect(data.length).toEqual(ITEMS); + sub.unsubscribe(); + done(); + }) + ); + })(); + }); + + it('should handle multiple subscriptions (warm)', done => { + (async () => { + const ITEMS = 4; + const { stocks } = await collectionHarness(afs, ITEMS); + const changes = TestBed.runInInjectionContext(() => stocks.valueChanges()); + changes.pipe(take(1)).subscribe(() => undefined).add(() => { + changes.pipe(take(1)).subscribe(data => { + expect(data.length).toEqual(ITEMS); + done(); + }); + }); + })(); + }); + + it('should handle dynamic queries that return empty sets', done => { + (async () => { + const ITEMS = 10; + let count = 0; + + const pricefilter$ = new BehaviorSubject(null); + const randomCollectionName = randomName(afs.firestore); + const ref = afs.firestore.collection(`${randomCollectionName}`); + await createRandomStocks(afs.firestore, ref, ITEMS); + const sub = pricefilter$.pipe(switchMap(price => { + return TestBed.runInInjectionContext(() => afs.collection(randomCollectionName, ref => price ? ref.where('price', '==', price) : ref).valueChanges()); + })).subscribe(data => { + count = count + 1; + // the first time should all be 'added' + if (count === 1) { + expect(data.length).toEqual(ITEMS); + pricefilter$.next(-1); + } + // on the second round, we should have filtered out everything + if (count === 2) { + expect(data.length).toEqual(0); + sub.unsubscribe(); + done(); + } + }); + })(); + }); + + }); + + describe('snapshotChanges()', () => { + + it('should listen to all snapshotChanges() by default', done => { + (async () => { + const ITEMS = 10; + let count = 0; + const { stocks, names } = await collectionHarness(afs, ITEMS); + const firstName = names[0]; + const sub = TestBed.runInInjectionContext(() => stocks.snapshotChanges()).subscribe(data => { + count = count + 1; + // the first time should all be 'added' + if (count === 1) { + // make an update + stocks.doc(firstName).update({ price: 2 }); + } + // on the second round, make sure the array is still the same + // length but the updated item is now modified + if (count === 2) { + expect(data.length).toEqual(ITEMS); + const change = data.find(x => x.payload.doc.id === firstName); + expect(change.type).toEqual('modified'); + sub.unsubscribe(); + done(); + } + }); + })(); + }); + + it('should handle multiple subscriptions (hot)', done => { + async function setup() { + const ITEMS = 4; + const { stocks } = await collectionHarness(afs, ITEMS); + const changes$ = TestBed.runInInjectionContext(() => stocks.snapshotChanges()); + const sub = changes$.subscribe(() => undefined); + sub.add( + changes$.pipe(take(1)).subscribe(data => { + expect(data.length).toEqual(ITEMS); + sub.unsubscribe(); + done(); + }) + ); + } + setup() + }); + + it('should handle multiple subscriptions (warm)', done => { + (async () => { + const ITEMS = 4; + const { stocks } = await collectionHarness(afs, ITEMS); + const changes = TestBed.runInInjectionContext(() => stocks.snapshotChanges()); + changes.pipe(take(1)).subscribe(() => undefined).add(() => { + changes.pipe(take(1)).subscribe(data => { + expect(data.length).toEqual(ITEMS); + done(); + }); + }); + })(); + }); + + it('should update order on queries', done => { + (async () => { + const ITEMS = 10; + let count = 0; + let firstIndex = 0; + const { stocks, names } = + await collectionHarness(afs, ITEMS, ref => ref.orderBy('price', 'desc')); + const sub = TestBed.runInInjectionContext(() => stocks.snapshotChanges()).subscribe(data => { + count = count + 1; + // the first time should all be 'added' + if (count === 1) { + // make an update + firstIndex = data.find(d => d.payload.doc.id === names[0]).payload.newIndex; + stocks.doc(names[0]).update({ price: 2 }); + } + // on the second round, make sure the array is still the same + // length but the updated item is now modified + if (count === 2) { + expect(data.length).toEqual(ITEMS); + const change = data.find(x => x.payload.doc.id === names[0]); + expect(change.type).toEqual('modified'); + expect(change.payload.oldIndex).toEqual(firstIndex); + sub.unsubscribe(); + done(); + } + }); + })(); + }); + + it('should be able to filter snapshotChanges() types - modified', done => { + pending("TODO(jamesdaniels) find out why this is flaking with SDK v11"); + (async () => { + const ITEMS = 10; + const { stocks, names } = await collectionHarness(afs, ITEMS); + + const sub = TestBed.runInInjectionContext(() => stocks.snapshotChanges(['modified'])).pipe(skip(1)).subscribe(data => { + sub.unsubscribe(); + const change = data.find(x => x.payload.doc.id === names[0]); + expect(data.length).toEqual(1); + expect(change.payload.doc.data().price).toEqual(2); + expect(change.type).toEqual('modified'); + done(); + }); + + delayUpdate(stocks, names[0], { price: 2 }); + })(); + }); + + it('should be able to filter snapshotChanges() types - added', done => { + (async () => { + const ITEMS = 10; + const { ref, stocks } = await collectionHarness(afs, ITEMS); + + const nextId = ref.doc('a').id; + + const sub = TestBed.runInInjectionContext(() => stocks.snapshotChanges(['added'])).pipe(skip(1)).subscribe(data => { + sub.unsubscribe(); + const change = data.find(x => x.payload.doc.id === nextId); + expect(data.length).toEqual(ITEMS + 1); + expect(change.payload.doc.data().price).toEqual(2); + expect(change.type).toEqual('added'); + done(); + }); + + delayAdd(stocks, nextId, { price: 2 }); + })(); + }); + + /* TODO(jamesdaniels): revisit this now that we have metadata + it('should be able to filter snapshotChanges() types - added/modified', done => { + (async () => { + const ITEMS = 10; + const harness = await collectionHarness(afs, ITEMS); + const { ref, stocks } = harness; + let names = harness.names; + + const nextId = ref.doc('a').id; + let count = 0; + + stocks.snapshotChanges(['added', 'modified']).pipe(skip(1), take(2)).subscribe(data => { + count += 1; + if (count === 1) { + const change = data.filter(x => x.payload.doc.id === nextId)[0]; + expect(data.length).toEqual(ITEMS + 1); + expect(change.payload.doc.data().price).toEqual(2); + expect(change.type).toEqual('added'); + delayUpdate(stocks, names[0], { price: 2 }); + } + if (count === 2) { + const change = data.filter(x => x.payload.doc.id === names[0])[0]; + expect(data.length).toEqual(ITEMS + 1); + expect(change.payload.doc.data().price).toEqual(2); + expect(change.type).toEqual('modified'); + } + }).add(() => { + deleteThemAll(names, ref) + .then(() => { + done() + }) + .catch(() => { + done.fail() + }); + }); + + names = names.concat([nextId]); + delayAdd(stocks, nextId, { price: 2 }); + })(); + }); + */ + + it('should be able to filter snapshotChanges() types - removed', done => { + (async () => { + const ITEMS = 10; + const { stocks, names } = await collectionHarness(afs, ITEMS); + + const sub = TestBed.runInInjectionContext(() => stocks.snapshotChanges(['added', 'removed'])).pipe(skip(1)).subscribe(data => { + sub.unsubscribe(); + const change = data.filter(x => x.payload.doc.id === names[0]); + expect(data.length).toEqual(ITEMS - 1); + expect(change.length).toEqual(0); + done(); + }); + + delayDelete(stocks, names[0], 400); + })(); + }); + + }); + + describe('stateChanges()', () => { + + it('should get stateChanges() updates', done => { + (async () => { + const ITEMS = 10; + const { stocks } = await collectionHarness(afs, ITEMS); + + const sub = TestBed.runInInjectionContext(() => stocks.stateChanges()).subscribe(data => { + // unsub immediately as we will be deleting data at the bottom + // and that will trigger another subscribe callback and fail + // the test + sub.unsubscribe(); + // We added ten things. This should be ten. + // This could not be ten if the batch failed or + // if the collection state is altered during a test run + expect(data.length).toEqual(ITEMS); + data.forEach(action => { + // We used the same piece of data so they should all equal + expect(action.payload.doc.data()).toEqual(FAKE_STOCK_DATA); + }); + done(); + }); + })(); + }); + + it('should listen to all stateChanges() by default', done => { + (async () => { + const ITEMS = 10; + let count = 0; + const { stocks, names } = await collectionHarness(afs, ITEMS); + TestBed.runInInjectionContext(() => stocks.stateChanges()).subscribe(data => { + count = count + 1; + if (count === 1) { + stocks.doc(names[0]).update({ price: 2 }); + } + if (count === 2) { + expect(data.length).toEqual(1); + expect(data[0].type).toEqual('modified'); + done(); + } + }); + })(); + }); + + it('should handle multiple subscriptions (hot)', done => { + (async () => { + const ITEMS = 4; + const { stocks } = await collectionHarness(afs, ITEMS); + const changes = TestBed.runInInjectionContext(() => stocks.stateChanges()); + const sub = changes.subscribe(() => undefined); + sub.add( + changes.pipe(take(1)).subscribe(data => { + expect(data.length).toEqual(ITEMS); + sub.unsubscribe(); + done(); + }) + ); + })(); + }); + + it('should handle multiple subscriptions (warm)', done => { + (async () => { + const ITEMS = 4; + const { stocks } = await collectionHarness(afs, ITEMS); + const changes = TestBed.runInInjectionContext(() => stocks.stateChanges()); + changes.pipe(take(1)).subscribe(() => undefined).add(() => { + changes.pipe(take(1)).subscribe(data => { + expect(data.length).toEqual(ITEMS); + done(); + }); + }); + })(); + }); + + it('should be able to filter stateChanges() types - modified', done => { + pending("TODO(jamesdaniels) find out why this is flaking with SDK v11"); + (async () => { + const ITEMS = 10; + const { stocks, names } = await collectionHarness(afs, ITEMS); + + const sub = TestBed.runInInjectionContext(() => stocks.stateChanges(['modified'])).pipe(skip(1), take(1)).subscribe(data => { + sub.unsubscribe(); + expect(data.length).toEqual(1); + expect(data[0].payload.doc.data().price).toEqual(2); + expect(data[0].type).toEqual('modified'); + done(); + }); + + delayUpdate(stocks, names[0], { price: 2 }); + })(); + }); + + it('should be able to filter stateChanges() types - added', done => { + (async () => { + const ITEMS = 10; + + const { ref, stocks } = await collectionHarness(afs, ITEMS); + + const sub = TestBed.runInInjectionContext(() => stocks.stateChanges(['added'])).pipe(skip(1)).subscribe(data => { + sub.unsubscribe(); + expect(data.length).toEqual(1); + expect(data[0].payload.doc.data().price).toEqual(2); + expect(data[0].type).toEqual('added'); + done(); + }); + + const nextId = ref.doc('a').id; + delayAdd(stocks, nextId, { price: 2 }); + })(); + }); + + it('should be able to filter stateChanges() types - removed', done => { + (async () => { + const ITEMS = 10; + const { stocks, names } = await collectionHarness(afs, ITEMS); + + const sub = TestBed.runInInjectionContext(() => stocks.stateChanges(['removed'])).pipe(skip(1), take(1)).subscribe(data => { + sub.unsubscribe(); + expect(data.length).toEqual(1); + expect(data[0].type).toEqual('removed'); + done(); + }); + + delayDelete(stocks, names[0], 400); + })(); + }); + + it('stateChanges() should emit on empty collection', done => { + TestBed.runInInjectionContext(() => afs.collection('EMPTY_COLLECTION').stateChanges()).pipe(take(1)).subscribe(data => { + expect(data.length).toEqual(0); + done(); + }); + }); + + it('stateChanges() w/filter should emit on empty collection', done => { + TestBed.runInInjectionContext(() => afs.collection('EMPTY_COLLECTION').stateChanges(['added'])).pipe(take(1)).subscribe(data => { + expect(data.length).toEqual(0); + done(); + }); + }); + + }); + + describe('auditTrail()', () => { + it('should listen to all events for auditTrail() by default', done => { + (async () => { + const ITEMS = 10; + let count = 0; + const { stocks, names } = await collectionHarness(afs, ITEMS); + const sub =TestBed.runInInjectionContext(() => stocks.auditTrail()).subscribe(data => { + count = count + 1; + if (count === 1) { + stocks.doc(names[0]).update({ price: 2 }); + } + if (count === 2) { + sub.unsubscribe(); + expect(data.length).toEqual(ITEMS + 1); + expect(data[data.length - 1].type).toEqual('modified'); + done(); + } + }); + })(); + }); + + it('should be able to filter auditTrail() types - removed', done => { + (async () => { + const ITEMS = 10; + const { stocks, names } = await collectionHarness(afs, ITEMS); + + const sub = TestBed.runInInjectionContext(() => stocks.auditTrail(['removed'])).pipe(skip(1), take(1)).subscribe(data => { + sub.unsubscribe(); + expect(data.length).toEqual(1); + expect(data[0].type).toEqual('removed'); + done(); + }); + + delayDelete(stocks, names[0], 400); + })(); + }); + }); + +}); diff --git a/src/firestore/collection/collection.ts b/src/compat/firestore/collection/collection.ts similarity index 71% rename from src/firestore/collection/collection.ts rename to src/compat/firestore/collection/collection.ts index 94a953577..50661fb44 100644 --- a/src/firestore/collection/collection.ts +++ b/src/compat/firestore/collection/collection.ts @@ -1,12 +1,15 @@ -import { from, Observable } from 'rxjs'; -import { fromCollectionRef } from '../observable/fromRef'; -import { filter, map, observeOn, scan } from 'rxjs/operators'; -import { firestore } from 'firebase/app'; - -import { CollectionReference, DocumentChangeAction, DocumentChangeType, DocumentData, DocumentReference, Query } from '../interfaces'; -import { docChanges, sortedChanges } from './changes'; +import { EnvironmentInjector, inject } from '@angular/core'; +import { pendingUntilEvent } from '@angular/core/rxjs-interop'; +import firebase from 'firebase/compat/app'; +import { Observable, from } from 'rxjs'; +import { filter, map, pairwise, scan, startWith } from 'rxjs/operators'; import { AngularFirestoreDocument } from '../document/document'; import { AngularFirestore } from '../firestore'; +import { CollectionReference, DocumentChangeAction, DocumentChangeType, DocumentData, DocumentReference, Query } from '../interfaces'; +import { fromCollectionRef } from '../observable/fromRef'; +import { docChanges, sortedChanges } from './changes'; + +type DocumentChangeTuple = [DocumentChangeAction[], DocumentChangeAction[]]; export function validateEventsArray(events?: DocumentChangeType[]) { if (!events || events.length === 0) { @@ -38,7 +41,9 @@ export function validateEventsArray(events?: DocumentChangeType[]) { * // Subscribe to changes as snapshots. This provides you data updates as well as delta updates. * fakeStock.valueChanges().subscribe(value => console.log(value)); */ -export class AngularFirestoreCollection { +export class AngularFirestoreCollection { + private readonly injector = inject(EnvironmentInjector); + /** * The constructor takes in a CollectionReference and Query to provide wrapper methods * for data operations and data streaming. @@ -49,8 +54,8 @@ export class AngularFirestoreCollection { * on this implication. */ constructor( - public readonly ref: CollectionReference, - private readonly query: Query, + public readonly ref: CollectionReference, + private readonly query: Query, private readonly afs: AngularFirestore) { } /** @@ -59,15 +64,20 @@ export class AngularFirestoreCollection { * your own data structure. */ stateChanges(events?: DocumentChangeType[]): Observable[]> { - if (!events || events.length === 0) { - return docChanges(this.query, this.afs.schedulers.outsideAngular).pipe( - this.afs.keepUnstableUntilFirst + let source = docChanges(this.query, this.afs.schedulers.outsideAngular); + if (events && events.length > 0) { + source = source.pipe( + map(actions => actions.filter(change => events.includes(change.type))) ); } - return docChanges(this.query, this.afs.schedulers.outsideAngular).pipe( - map(actions => actions.filter(change => events.indexOf(change.type) > -1)), - filter(changes => changes.length > 0), - this.afs.keepUnstableUntilFirst + return source.pipe( + // We want to filter out empty arrays, but always emit at first, so the developer knows + // that the collection has been resolve; even if it's empty + startWith[], undefined>(undefined), + pairwise(), + filter(([prior, current]: DocumentChangeTuple) => current.length > 0 || !prior), + map(([, current]) => current), + pendingUntilEvent(this.injector) ); } @@ -87,7 +97,7 @@ export class AngularFirestoreCollection { const validatedEvents = validateEventsArray(events); const scheduledSortedChanges$ = sortedChanges(this.query, validatedEvents, this.afs.schedulers.outsideAngular); return scheduledSortedChanges$.pipe( - this.afs.keepUnstableUntilFirst + pendingUntilEvent(this.injector) ); } @@ -98,32 +108,32 @@ export class AngularFirestoreCollection { * provided `idField` property name. */ valueChanges(): Observable; - // tslint:disable-next-line:unified-signatures + // eslint-disable-next-line no-empty-pattern valueChanges({}): Observable; - valueChanges(options: {idField: K}): Observable<(T & { [T in K]: string })[]>; + valueChanges(options: {idField: K}): Observable<(T & Record)[]>; valueChanges(options: {idField?: K} = {}): Observable { return fromCollectionRef(this.query, this.afs.schedulers.outsideAngular) .pipe( map(actions => actions.payload.docs.map(a => { if (options.idField) { return { - ...a.data() as {}, + ...a.data() as any, ...{ [options.idField]: a.id } - } as T & { [T in K]: string }; + } as T & Record; } else { return a.data(); } })), - this.afs.keepUnstableUntilFirst + pendingUntilEvent(this.injector) ); } /** * Retrieve the results of the query once. */ - get(options?: firestore.GetOptions) { + get(options?: firebase.firestore.GetOptions) { return from(this.query.get(options)).pipe( - observeOn(this.afs.schedulers.insideAngular), + pendingUntilEvent(this.injector) ); } @@ -134,14 +144,15 @@ export class AngularFirestoreCollection { * when you update data it is not updating data to the window of your query unless * the data fits the criteria of the query. */ - add(data: T): Promise { + add(data: T): Promise> { return this.ref.add(data); } /** * Create a reference to a single document in a collection. */ - doc(path?: string): AngularFirestoreDocument { - return new AngularFirestoreDocument(this.ref.doc(path), this.afs); + doc(path?: string): AngularFirestoreDocument { + // TODO is there a better way to solve this type issue + return new AngularFirestoreDocument(this.ref.doc(path) as any, this.afs); } } diff --git a/src/compat/firestore/document/document.spec.ts b/src/compat/firestore/document/document.spec.ts new file mode 100644 index 000000000..d1ab1b5df --- /dev/null +++ b/src/compat/firestore/document/document.spec.ts @@ -0,0 +1,99 @@ +import { TestBed } from '@angular/core/testing'; +import { AngularFireModule } from '@angular/fire/compat'; +import { AngularFirestore, AngularFirestoreDocument, AngularFirestoreModule, DocumentReference, USE_EMULATOR } from '@angular/fire/compat/firestore'; +import firebase from 'firebase/compat/app'; +import { take } from 'rxjs/operators'; +import { COMMON_CONFIG, firestoreEmulatorPort } from '../../../test-config'; +import { rando } from '../../../utils'; +import { FAKE_STOCK_DATA, Stock, randomName } from '../utils.spec'; +import 'firebase/compat/firestore'; + +// TODO(davideast): Investage this flake on Safari. +describe('AngularFirestoreDocument', () => { + let afs: AngularFirestore; + + beforeEach(() => { + pending("These are pretty broken, investigate."); + + TestBed.configureTestingModule({ + imports: [ + AngularFireModule.initializeApp(COMMON_CONFIG, rando()), + AngularFirestoreModule + ], + providers: [ + { provide: USE_EMULATOR, useValue: ['localhost', firestoreEmulatorPort] } + ] + }); + + afs = TestBed.inject(AngularFirestore); + }); + + describe('valueChanges()', () => { + + it('should get unwrapped snapshot', done => { + (async () => { + const randomCollectionName = TestBed.runInInjectionContext(() => afs.firestore.collection('a').doc().id); + const ref = TestBed.runInInjectionContext(() => afs.firestore.doc(`${randomCollectionName}/FAKE`)) as firebase.firestore.DocumentReference; + const stock = new AngularFirestoreDocument(ref, afs); + await TestBed.runInInjectionContext(() => stock.set(FAKE_STOCK_DATA)); + const obs$ = TestBed.runInInjectionContext(() => stock.valueChanges()); + obs$.pipe(take(1)).subscribe(data => { + expect(data).toEqual(FAKE_STOCK_DATA); + done(); + }); + })(); + }); + + /* TODO(jamesdaniels): test is flaking, look into this + it('should optionally map the doc ID to the emitted data object', done => { + (async () => { + const randomCollectionName = afs.firestore.collection('a').doc().id; + const ref = afs.firestore.doc(`${randomCollectionName}/FAKE`); + const stock = new AngularFirestoreDocument(ref, afs); + await stock.set(FAKE_STOCK_DATA); + const idField = 'myCustomID'; + const obs$ = stock.valueChanges({ idField }); + obs$.pipe(take(1)).subscribe(async data => { + expect(data[idField]).toBeDefined(); + expect(data).toEqual(jasmine.objectContaining(FAKE_STOCK_DATA)); + stock.delete().then(done).catch(done.fail); + }); + })(); + });*/ + + }); + + describe('snapshotChanges()', () => { + + it('should get action updates', done => { + (async () => { + const randomCollectionName = randomName(afs.firestore); + const ref = TestBed.runInInjectionContext(() => afs.firestore.doc(`${randomCollectionName}/FAKE`)) as DocumentReference; + const stock = new AngularFirestoreDocument(ref, afs); + await TestBed.runInInjectionContext(() => stock.set(FAKE_STOCK_DATA)); + const sub = TestBed.runInInjectionContext(() => stock.snapshotChanges()).subscribe(a => { + sub.unsubscribe(); + expect(a.payload.exists).toBeTrue(); + expect(a.payload.data()).toEqual(FAKE_STOCK_DATA); + done(); + }); + })(); + }); + + it('should get unwrapped snapshot', done => { + (async () => { + const randomCollectionName = afs.firestore.collection('a').doc().id; + const ref = TestBed.runInInjectionContext(() => afs.firestore.doc(`${randomCollectionName}/FAKE`)) as DocumentReference; + const stock = new AngularFirestoreDocument(ref, afs); + await TestBed.runInInjectionContext(() => stock.set(FAKE_STOCK_DATA)); + const obs$ = TestBed.runInInjectionContext(() => stock.valueChanges()); + obs$.pipe(take(1)).subscribe(data => { + expect(data).toEqual(FAKE_STOCK_DATA); + done(); + }); + })(); + }); + + }); + +}); diff --git a/src/firestore/document/document.ts b/src/compat/firestore/document/document.ts similarity index 61% rename from src/firestore/document/document.ts rename to src/compat/firestore/document/document.ts index 8592c5a5f..cacbe3722 100644 --- a/src/firestore/document/document.ts +++ b/src/compat/firestore/document/document.ts @@ -1,10 +1,12 @@ -import { from, Observable } from 'rxjs'; +import { EnvironmentInjector, inject } from '@angular/core'; +import { pendingUntilEvent } from '@angular/core/rxjs-interop'; +import firebase from 'firebase/compat/app'; +import { Observable, from } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { AngularFirestoreCollection } from '../collection/collection'; +import { AngularFirestore, associateQuery } from '../firestore'; import { Action, DocumentData, DocumentReference, DocumentSnapshot, QueryFn, SetOptions } from '../interfaces'; import { fromDocRef } from '../observable/fromRef'; -import { map, observeOn } from 'rxjs/operators'; -import { AngularFirestore, associateQuery } from '../firestore'; -import { AngularFirestoreCollection } from '../collection/collection'; -import { firestore } from 'firebase/app'; /** * AngularFirestoreDocument service @@ -28,13 +30,14 @@ import { firestore } from 'firebase/app'; * // OR! Transform using Observable.from() and the data is unwrapped for you * Observable.from(fakeStock).subscribe(value => console.log(value)); */ -export class AngularFirestoreDocument { +export class AngularFirestoreDocument { + private readonly injector = inject(EnvironmentInjector); /** - * The contstuctor takes in a DocumentReference to provide wrapper methods + * The constructor takes in a DocumentReference to provide wrapper methods * for data operations, data streaming, and Symbol.observable. */ - constructor(public ref: DocumentReference, private afs: AngularFirestore) { } + constructor(public ref: DocumentReference, private afs: AngularFirestore) { } /** * Create or overwrite a single document. @@ -61,10 +64,10 @@ export class AngularFirestoreDocument { * Create a reference to a sub-collection given a path and an optional query * function. */ - collection(path: string, queryFn?: QueryFn): AngularFirestoreCollection { - const collectionRef = this.ref.collection(path); + collection(path: string, queryFn?: QueryFn): AngularFirestoreCollection { + const collectionRef = this.ref.collection(path) as firebase.firestore.CollectionReference; const { ref, query } = associateQuery(collectionRef, queryFn); - return new AngularFirestoreCollection(ref, query, this.afs); + return new AngularFirestoreCollection(ref, query, this.afs); } /** @@ -73,27 +76,35 @@ export class AngularFirestoreDocument { snapshotChanges(): Observable>> { const scheduledFromDocRef$ = fromDocRef(this.ref, this.afs.schedulers.outsideAngular); return scheduledFromDocRef$.pipe( - this.afs.keepUnstableUntilFirst + pendingUntilEvent(this.injector) ); } /** * Listen to unwrapped snapshot updates from the document. + * + * If the `idField` option is provided, document IDs are included and mapped to the + * provided `idField` property name. */ - valueChanges(): Observable { + valueChanges(options?: unknown): Observable; + valueChanges(options: { idField: K }): Observable<(T & Record) | undefined>; + valueChanges(options: { idField?: K } = {}): Observable { return this.snapshotChanges().pipe( - map(action => { - return action.payload.data(); - }) + map(({ payload }) => + options.idField ? { + ...payload.data(), + ...{ [options.idField]: payload.id } + } as T & Record : payload.data() + ) ); } /** * Retrieve the document once. */ - get(options?: firestore.GetOptions) { + get(options?: firebase.firestore.GetOptions) { return from(this.ref.get(options)).pipe( - observeOn(this.afs.schedulers.insideAngular), + pendingUntilEvent(this.injector) ); } } diff --git a/src/compat/firestore/firestore.module.ts b/src/compat/firestore/firestore.module.ts new file mode 100644 index 000000000..ec1f916ac --- /dev/null +++ b/src/compat/firestore/firestore.module.ts @@ -0,0 +1,26 @@ +import { ModuleWithProviders, NgModule } from '@angular/core'; +import { VERSION } from '@angular/fire'; +import firebase from 'firebase/compat/app'; +import { AngularFirestore, ENABLE_PERSISTENCE, PERSISTENCE_SETTINGS } from './firestore'; +import { PersistenceSettings } from './interfaces'; + +@NgModule({ + providers: [ AngularFirestore ] +}) +export class AngularFirestoreModule { + constructor() { + firebase.registerVersion('angularfire', VERSION.full, 'fst-compat'); + } + /** + * Attempt to enable persistent storage, if possible + */ + static enablePersistence(persistenceSettings?: PersistenceSettings): ModuleWithProviders { + return { + ngModule: AngularFirestoreModule, + providers: [ + { provide: ENABLE_PERSISTENCE, useValue: true }, + { provide: PERSISTENCE_SETTINGS, useValue: persistenceSettings }, + ] + }; + } +} diff --git a/src/compat/firestore/firestore.spec.ts b/src/compat/firestore/firestore.spec.ts new file mode 100644 index 000000000..70d91e17d --- /dev/null +++ b/src/compat/firestore/firestore.spec.ts @@ -0,0 +1,153 @@ +import { TestBed } from '@angular/core/testing'; +import { AngularFireModule, FIREBASE_APP_NAME, FIREBASE_OPTIONS } from '@angular/fire/compat'; +import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument, AngularFirestoreModule, USE_EMULATOR } from '@angular/fire/compat/firestore'; +import { COMMON_CONFIG, firestoreEmulatorPort } from '../../../src/test-config'; +import 'firebase/compat/firestore'; +import { rando } from '../../../src/utils'; + +describe('AngularFirestore', () => { + let afs: AngularFirestore; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + AngularFireModule.initializeApp(COMMON_CONFIG, rando()), + AngularFirestoreModule.enablePersistence() + ], + providers: [ + { provide: USE_EMULATOR, useValue: ['localhost', firestoreEmulatorPort] } + ] + }); + + afs = TestBed.inject(AngularFirestore); + }); + + it('should be the properly initialized type', () => { + expect(afs instanceof AngularFirestore).toBe(true); + }); + + it('should have an initialized Firebase app', () => { + expect(TestBed.runInInjectionContext(() => afs.firestore.app)).toBeDefined(); + }); + + it('should create an AngularFirestoreDocument from a string path', () => { + const doc = TestBed.runInInjectionContext(() => afs.doc('a/doc')); + expect(doc instanceof AngularFirestoreDocument).toBe(true); + }); + + it('should create an AngularFirestoreDocument from a string path', () => { + const doc = TestBed.runInInjectionContext(() => afs.doc(afs.doc('a/doc').ref)); + expect(doc instanceof AngularFirestoreDocument).toBe(true); + }); + + it('should create an AngularFirestoreCollection from a string path', () => { + const collection = TestBed.runInInjectionContext(() => afs.collection('stuffs')); + expect(collection instanceof AngularFirestoreCollection).toBe(true); + }); + + it('should create an AngularFirestoreCollection from a reference', () => { + const collection = TestBed.runInInjectionContext(() => afs.collection(afs.collection('stuffs').ref)); + expect(collection instanceof AngularFirestoreCollection).toBe(true); + }); + + it('should throw on an invalid document path', () => { + const singleWrapper = () => TestBed.runInInjectionContext(() => afs.doc('collection')); + const tripleWrapper = () => TestBed.runInInjectionContext(() => afs.doc('collection/doc/subcollection')); + expect(singleWrapper).toThrowError(); + expect(tripleWrapper).toThrowError(); + }); + + it('should throw on an invalid collection path', () => { + const singleWrapper = () => TestBed.runInInjectionContext(() => afs.collection('collection/doc')); + const quadWrapper = () => TestBed.runInInjectionContext(() => afs.collection('collection/doc/subcollection/doc')); + expect(singleWrapper).toThrowError(); + expect(quadWrapper).toThrowError(); + }); + + if (typeof window === 'undefined') { + + it('should not enable persistence (Node.js)', (done) => { + TestBed.runInInjectionContext(() => afs.persistenceEnabled$).subscribe(isEnabled => { + expect(isEnabled).toBe(false); + done(); + }); + }); + + } else { + + it('should enable persistence', (done) => { + TestBed.runInInjectionContext(() => afs.persistenceEnabled$).subscribe(isEnabled => { + expect(isEnabled).toBe(true); + done(); + }); + }); + + } + +}); + +describe('AngularFirestore with different app', () => { + let afs: AngularFirestore; + let firebaseAppName: string; + + beforeEach(() => { + firebaseAppName = rando(); + TestBed.configureTestingModule({ + imports: [ + AngularFireModule.initializeApp(COMMON_CONFIG, rando()), + AngularFirestoreModule + ], + providers: [ + { provide: FIREBASE_APP_NAME, useValue: firebaseAppName }, + { provide: FIREBASE_OPTIONS, useValue: COMMON_CONFIG }, + { provide: USE_EMULATOR, useValue: ['localhost', firestoreEmulatorPort] } + ] + }); + + + afs = TestBed.inject(AngularFirestore); + }); + + describe('', () => { + + it('should be an AngularFirestore type', () => { + expect(afs instanceof AngularFirestore).toEqual(true); + }); + + it('should have an initialized Firebase app', () => { + expect(TestBed.runInInjectionContext(() => afs.firestore.app)).toBeDefined(); + }); + + it('should have an initialized Firebase app instance member', () => { + expect(TestBed.runInInjectionContext(() => afs.firestore.app.name)).toEqual(firebaseAppName); + }); + }); + +}); + + +describe('AngularFirestore without persistance', () => { + let afs: AngularFirestore; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + AngularFireModule.initializeApp(COMMON_CONFIG, rando()), + AngularFirestoreModule + ], + providers: [ + { provide: USE_EMULATOR, useValue: ['localhost', firestoreEmulatorPort] } + ] + }); + + afs = TestBed.inject(AngularFirestore); + }); + + it('should not enable persistence', (done) => { + TestBed.runInInjectionContext(() => afs.persistenceEnabled$).subscribe(isEnabled => { + expect(isEnabled).toBe(false); + done(); + }); + }); + +}); diff --git a/src/compat/firestore/firestore.ts b/src/compat/firestore/firestore.ts new file mode 100644 index 000000000..fcaa9441b --- /dev/null +++ b/src/compat/firestore/firestore.ts @@ -0,0 +1,241 @@ +import { isPlatformServer } from '@angular/common'; +import { Inject, Injectable, InjectionToken, NgZone, Optional, PLATFORM_ID, inject } from '@angular/core'; +import { ÉĩAngularFireSchedulers } from '@angular/fire'; +import { AppCheckInstances } from '@angular/fire/app-check'; +import { FIREBASE_APP_NAME, FIREBASE_OPTIONS, ÉĩcacheInstance, ÉĩfirebaseAppFactory } from '@angular/fire/compat'; +import { + SETTINGS as AUTH_SETTINGS, + AngularFireAuth, + LANGUAGE_CODE, + PERSISTENCE, + TENANT_ID, + USE_EMULATOR as USE_AUTH_EMULATOR, + USE_DEVICE_LANGUAGE, + ÉĩauthFactory, +} from '@angular/fire/compat/auth'; +import { FirebaseOptions } from 'firebase/app'; +import firebase from 'firebase/compat/app'; +import { Observable, from, of } from 'rxjs'; +import { AngularFirestoreCollection } from './collection/collection'; +import { AngularFirestoreCollectionGroup } from './collection-group/collection-group'; +import { AngularFirestoreDocument } from './document/document'; +import { + AssociatedReference, + CollectionReference, + DocumentReference, + PersistenceSettings, + Query, + QueryFn, + QueryGroupFn, + Settings +} from './interfaces'; +import 'firebase/compat/auth'; +import 'firebase/compat/firestore'; + +/** + * The value of this token determines whether or not the firestore will have persistance enabled + */ +export const ENABLE_PERSISTENCE = new InjectionToken('angularfire2.enableFirestorePersistence'); +export const PERSISTENCE_SETTINGS = new InjectionToken('angularfire2.firestore.persistenceSettings'); +export const SETTINGS = new InjectionToken('angularfire2.firestore.settings'); + +type UseEmulatorArguments = Parameters; +export const USE_EMULATOR = new InjectionToken('angularfire2.firestore.use-emulator'); + +/** + * A utility methods for associating a collection reference with + * a query. + * + * @param collectionRef - A collection reference to query + * @param queryFn - The callback to create a query + * + * Example: + * const { query, ref } = associateQuery(docRef.collection('items'), ref => { + * return ref.where('age', '<', 200); + * }); + */ +export function associateQuery(collectionRef: CollectionReference, queryFn = ref => ref): AssociatedReference { + const query = queryFn(collectionRef); + const ref = collectionRef; + return { query, ref }; +} + +/** + * AngularFirestore Service + * + * This service is the main entry point for this feature module. It provides + * an API for creating Collection and Reference services. These services can + * then be used to do data updates and observable streams of the data. + * + * Example: + * + * import { Component } from '@angular/core'; + * import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from '@angular/fire/firestore'; + * import { Observable } from 'rxjs/Observable'; + * import { from } from 'rxjs/observable'; + * + * @Component({ + * selector: 'app-my-component', + * template: ` + *

Items for {{ (profile | async)?.name }} + *
    + *
  • {{ item.name }}
  • + *
+ *
+ * + * + *
+ * ` + * }) + * export class MyComponent implements OnInit { + * + * // services for data operations and data streaming + * private readonly itemsRef: AngularFirestoreCollection; + * private readonly profileRef: AngularFirestoreDocument; + * + * // observables for template + * items: Observable; + * profile: Observable; + * + * // inject main service + * constructor(private readonly afs: AngularFirestore) {} + * + * ngOnInit() { + * this.itemsRef = afs.collection('items', ref => ref.where('user', '==', 'davideast').limit(10)); + * this.items = this.itemsRef.valueChanges().map(snap => snap.docs.map(data => doc.data())); + * // this.items = from(this.itemsRef); // you can also do this with no mapping + * + * this.profileRef = afs.doc('users/davideast'); + * this.profile = this.profileRef.valueChanges(); + * } + * + * addItem(name: string) { + * const user = 'davideast'; + * this.itemsRef.add({ name, user }); + * } + * } + */ +@Injectable({ + providedIn: 'any' +}) +export class AngularFirestore { + public readonly firestore: firebase.firestore.Firestore; + public readonly persistenceEnabled$: Observable; + private readonly ngZone = inject(NgZone); + + /** + * Each Feature of AngularFire has a FirebaseApp injected. This way we + * don't rely on the main Firebase App instance and we can create named + * apps and use multiple apps. + */ + constructor( + @Inject(FIREBASE_OPTIONS) options: FirebaseOptions, + @Optional() @Inject(FIREBASE_APP_NAME) name: string | null | undefined, + @Optional() @Inject(ENABLE_PERSISTENCE) shouldEnablePersistence: boolean | null, + @Optional() @Inject(SETTINGS) settings: Settings | null, + + @Inject(PLATFORM_ID) platformId: object, + zone: NgZone, + public schedulers: ÉĩAngularFireSchedulers, + @Optional() @Inject(PERSISTENCE_SETTINGS) persistenceSettings: PersistenceSettings | null, + @Optional() @Inject(USE_EMULATOR) _useEmulator: any, + @Optional() auth: AngularFireAuth, + @Optional() @Inject(USE_AUTH_EMULATOR) useAuthEmulator: any, + @Optional() @Inject(AUTH_SETTINGS) authSettings: any, // can't use firebase.auth.AuthSettings here + @Optional() @Inject(TENANT_ID) tenantId: string | null, + @Optional() @Inject(LANGUAGE_CODE) languageCode: string | null, + @Optional() @Inject(USE_DEVICE_LANGUAGE) useDeviceLanguage: boolean | null, + @Optional() @Inject(PERSISTENCE) persistence: string | null, + @Optional() _appCheckInstances: AppCheckInstances, + ) { + const app = ÉĩfirebaseAppFactory(options, zone, name); + const useEmulator: UseEmulatorArguments | null = _useEmulator; + + if (auth) { + ÉĩauthFactory(app, zone, useAuthEmulator, tenantId, languageCode, useDeviceLanguage, authSettings, persistence); + } + + [this.firestore, this.persistenceEnabled$] = ÉĩcacheInstance(`${app.name}.firestore`, 'AngularFirestore', app.name, () => { + const firestore = zone.runOutsideAngular(() => app.firestore()); + if (settings) { + firestore.settings(settings); + } + if (useEmulator) { + firestore.useEmulator(...useEmulator); + } + + if (shouldEnablePersistence && !isPlatformServer(platformId)) { + // We need to try/catch here because not all enablePersistence() failures are caught + // https://github.com/firebase/firebase-js-sdk/issues/608 + const enablePersistence = () => { + try { + return from(firestore.enablePersistence(persistenceSettings || undefined).then(() => true, () => false)); + } catch (e) { + if (typeof console !== 'undefined') { console.warn(e); } + return of(false); + } + }; + return [firestore, zone.runOutsideAngular(enablePersistence)]; + } else { + return [firestore, of(false)]; + } + + }, [settings, useEmulator, shouldEnablePersistence]); + } + + /** + * Create a reference to a Firestore Collection based on a path or + * CollectionReference and an optional query function to narrow the result + * set. + */ + collection(path: string, queryFn?: QueryFn): AngularFirestoreCollection; + collection(ref: CollectionReference, queryFn?: QueryFn): AngularFirestoreCollection; + collection(pathOrRef: string | CollectionReference, queryFn?: QueryFn): AngularFirestoreCollection { + let collectionRef: CollectionReference; + if (typeof pathOrRef === 'string') { + collectionRef = this.firestore.collection(pathOrRef) as firebase.firestore.CollectionReference; + } else { + collectionRef = pathOrRef; + } + const { ref, query } = associateQuery(collectionRef, queryFn); + const refInZone = this.ngZone.run(() => ref); + return new AngularFirestoreCollection(refInZone, query, this); + } + + /** + * Create a reference to a Firestore Collection Group based on a collectionId + * and an optional query function to narrow the result + * set. + */ + collectionGroup(collectionId: string, queryGroupFn?: QueryGroupFn): AngularFirestoreCollectionGroup { + const queryFn = queryGroupFn || (ref => ref); + const collectionGroup: Query = this.firestore.collectionGroup(collectionId) as firebase.firestore.Query; + return new AngularFirestoreCollectionGroup(queryFn(collectionGroup), this); + } + + /** + * Create a reference to a Firestore Document based on a path or + * DocumentReference. Note that documents are not queryable because they are + * simply objects. However, documents have sub-collections that return a + * Collection reference and can be queried. + */ + doc(path: string): AngularFirestoreDocument; + doc(ref: DocumentReference): AngularFirestoreDocument; + doc(pathOrRef: string | DocumentReference): AngularFirestoreDocument { + let ref: DocumentReference; + if (typeof pathOrRef === 'string') { + ref = this.firestore.doc(pathOrRef) as firebase.firestore.DocumentReference; + } else { + ref = pathOrRef; + } + const refInZone = this.ngZone.run(() => ref); + return new AngularFirestoreDocument(refInZone, this); + } + + /** + * Returns a generated Firestore Document Id. + */ + createId() { + return this.firestore.collection('_').doc().id; + } +} diff --git a/src/compat/firestore/interfaces.ts b/src/compat/firestore/interfaces.ts new file mode 100644 index 000000000..a557a4531 --- /dev/null +++ b/src/compat/firestore/interfaces.ts @@ -0,0 +1,86 @@ +import firebase from 'firebase/compat/app'; +import { Subscriber } from 'rxjs'; + +export type Settings = firebase.firestore.Settings; +export type CollectionReference = firebase.firestore.CollectionReference; +export type DocumentReference = firebase.firestore.DocumentReference; +export type PersistenceSettings = firebase.firestore.PersistenceSettings; +export type DocumentChangeType = firebase.firestore.DocumentChangeType; +export type SnapshotOptions = firebase.firestore.SnapshotOptions; +export type FieldPath = firebase.firestore.FieldPath; +export type Query = firebase.firestore.Query; + +export type SetOptions = firebase.firestore.SetOptions; +export type DocumentData = firebase.firestore.DocumentData; + +export interface DocumentSnapshotExists extends firebase.firestore.DocumentSnapshot { + readonly exists: true; + data(options?: SnapshotOptions): T; +} + +export interface DocumentSnapshotDoesNotExist extends firebase.firestore.DocumentSnapshot { + readonly exists: false; + data(options?: SnapshotOptions): undefined; + get(fieldPath: string | FieldPath, options?: SnapshotOptions): undefined; +} + +export type DocumentSnapshot = DocumentSnapshotExists | DocumentSnapshotDoesNotExist; + +export interface QueryDocumentSnapshot extends firebase.firestore.QueryDocumentSnapshot { + data(options?: SnapshotOptions): T; +} + +export interface QuerySnapshot extends firebase.firestore.QuerySnapshot { + readonly docs: QueryDocumentSnapshot[]; +} + +export interface DocumentChange extends firebase.firestore.DocumentChange { + readonly doc: QueryDocumentSnapshot; +} + +export interface DocumentChangeAction { + type: DocumentChangeType; + payload: DocumentChange; +} + +export interface Action { + type: string; + payload: T; +} + +export interface Reference { + onSnapshot: (options: firebase.firestore.SnapshotListenOptions, sub: Subscriber) => any; +} + +// A convience type for making a query. +// Example: const query = (ref) => ref.where('name', == 'david'); +export type QueryFn = (ref: CollectionReference) => Query; + +export type QueryGroupFn = (query: Query) => Query; + +/** + * A structure that provides an association between a reference + * and a query on that reference. Note: Performing operations + * on the reference can lead to confusing results with complicated + * queries. + * + * Example: + * + * const query = ref.where('type', '==', 'Book'). + * .where('price', '>' 18.00) + * .where('price', '<' 100.00) + * .where('category', '==', 'Fiction') + * .where('publisher', '==', 'BigPublisher') + * + * // This addition would not be a result of the query above + * ref.add({ + * type: 'Magazine', + * price: 4.99, + * category: 'Sports', + * publisher: 'SportsPublisher' + * }); + */ +export interface AssociatedReference { + ref: CollectionReference; + query: Query; +} diff --git a/src/compat/firestore/ng-package.json b/src/compat/firestore/ng-package.json new file mode 100644 index 000000000..e5e5ccdfc --- /dev/null +++ b/src/compat/firestore/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/compat/firestore/observable/fromRef.ts b/src/compat/firestore/observable/fromRef.ts new file mode 100644 index 000000000..8d7ce4393 --- /dev/null +++ b/src/compat/firestore/observable/fromRef.ts @@ -0,0 +1,48 @@ +import { Observable, SchedulerLike, asyncScheduler } from 'rxjs'; +import { map, pairwise, startWith } from 'rxjs/operators'; +import { Action, DocumentReference, DocumentSnapshot, Query, QuerySnapshot, Reference } from '../interfaces'; + +function _fromRef(ref: Reference, scheduler: SchedulerLike = asyncScheduler): Observable { + return new Observable(subscriber => { + let unsubscribe: () => void; + if (scheduler != null) { + scheduler.schedule(() => { + unsubscribe = ref.onSnapshot({ includeMetadataChanges: true }, subscriber); + }); + } else { + unsubscribe = ref.onSnapshot({ includeMetadataChanges: true }, subscriber); + } + + return () => { + if (unsubscribe != null) { + unsubscribe(); + } + }; + }); +} + +export function fromRef(ref: DocumentReference | Query, scheduler?: SchedulerLike) { + return _fromRef(ref, scheduler); +} + +export function fromDocRef(ref: DocumentReference, scheduler?: SchedulerLike): Observable>> { + return fromRef, T>(ref, scheduler) + .pipe( + startWith, undefined>(undefined), + pairwise(), + map((snapshots: [DocumentSnapshot, DocumentSnapshot]) => { + const [priorPayload, payload] = snapshots; + if (!payload.exists) { + return { payload, type: 'removed' }; + } + if (!priorPayload?.exists) { + return { payload, type: 'added' }; + } + return { payload, type: 'modified' }; + }) + ); +} + +export function fromCollectionRef(ref: Query, scheduler?: SchedulerLike): Observable>> { + return fromRef, T>(ref, scheduler).pipe(map(payload => ({ payload, type: 'query' }))); +} diff --git a/src/compat/firestore/package.json b/src/compat/firestore/package.json new file mode 100644 index 000000000..8dce418a5 --- /dev/null +++ b/src/compat/firestore/package.json @@ -0,0 +1,3 @@ +{ + "$schema": "../../node_modules/ng-packagr/package.schema.json" +} diff --git a/src/compat/firestore/public_api.ts b/src/compat/firestore/public_api.ts new file mode 100644 index 000000000..1670f1451 --- /dev/null +++ b/src/compat/firestore/public_api.ts @@ -0,0 +1,8 @@ +export * from './firestore'; +export * from './firestore.module'; +export * from './collection/collection'; +export * from './collection-group/collection-group'; +export * from './document/document'; +export * from './collection/changes'; +export * from './observable/fromRef'; +export * from './interfaces'; diff --git a/src/compat/firestore/utils.spec.ts b/src/compat/firestore/utils.spec.ts new file mode 100644 index 000000000..88842d8a2 --- /dev/null +++ b/src/compat/firestore/utils.spec.ts @@ -0,0 +1,55 @@ +import { TestBed } from '@angular/core/testing'; +import { AngularFirestoreCollection } from '@angular/fire/compat/firestore'; +import firebase from 'firebase/compat/app'; + +export interface Stock { + name: string; + price: number; +} + +export const FAKE_STOCK_DATA = { name: 'FAKE', price: 1 }; + +export const randomName = (firestore): string => firestore.collection('a').doc().id; + +export const createRandomStocks = async ( + firestore: firebase.firestore.Firestore, + collectionRef: firebase.firestore.CollectionReference, + numberOfItems +) => { + // Create a batch to update everything at once + const batch = TestBed.runInInjectionContext(() => firestore.batch()); + // Store the random names to delete them later + let names: string[] = []; + Array.from(Array(numberOfItems)).forEach(() => { + const name = randomName(firestore); + TestBed.runInInjectionContext(() => batch.set(collectionRef.doc(name), FAKE_STOCK_DATA)); + names = [...names, name]; + }); + // Create the batch entries + // Commit! + await TestBed.runInInjectionContext(() => batch.commit()); + return names; +}; + +export function deleteThemAll(names, ref) { + const promises = names.map(name => ref.doc(name).delete()); + return Promise.all(promises); +} + +export function delayUpdate(collection: AngularFirestoreCollection|firebase.firestore.CollectionReference, path, data, delay = 250) { + setTimeout(() => { + TestBed.runInInjectionContext(() => collection.doc(path).update(data)); + }, delay); +} + +export function delayAdd(collection: AngularFirestoreCollection|firebase.firestore.CollectionReference, path, data, delay = 250) { + setTimeout(() => { + TestBed.runInInjectionContext(() => collection.doc(path).set(data)); + }, delay); +} + +export function delayDelete(collection: AngularFirestoreCollection|firebase.firestore.CollectionReference, path, delay = 250) { + setTimeout(() => { + TestBed.runInInjectionContext(() => collection.doc(path).delete()); + }, delay); +} diff --git a/src/compat/functions/base.ts b/src/compat/functions/base.ts new file mode 100644 index 000000000..ce221ce8e --- /dev/null +++ b/src/compat/functions/base.ts @@ -0,0 +1,7 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +// Export a null object with the same keys as firebase/compat/functions, so Proxy can work with proxy-polyfill in Internet Explorer +export const proxyPolyfillCompat = { + useEmulator: null, + useFunctionsEmulator: null, + httpsCallable: null, +}; diff --git a/src/compat/functions/functions.module.ts b/src/compat/functions/functions.module.ts new file mode 100644 index 000000000..f6cc15327 --- /dev/null +++ b/src/compat/functions/functions.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { VERSION } from '@angular/fire'; +import firebase from 'firebase/compat/app'; +import { AngularFireFunctions } from './functions'; + +@NgModule({ + providers: [ AngularFireFunctions ] +}) +export class AngularFireFunctionsModule { + constructor() { + firebase.registerVersion('angularfire', VERSION.full, 'fn-compat'); + } +} diff --git a/src/compat/functions/functions.spec.ts b/src/compat/functions/functions.spec.ts new file mode 100644 index 000000000..6b2bc892c --- /dev/null +++ b/src/compat/functions/functions.spec.ts @@ -0,0 +1,70 @@ +import { TestBed } from '@angular/core/testing'; +import { AngularFireModule, FIREBASE_APP_NAME, FIREBASE_OPTIONS } from '@angular/fire/compat'; +import { AngularFireFunctions, AngularFireFunctionsModule, REGION, USE_EMULATOR } from '@angular/fire/compat/functions'; +import { COMMON_CONFIG } from '../../../src/test-config'; +import 'firebase/compat/functions'; +import { rando } from '../../../src/utils'; + +describe('AngularFireFunctions', () => { + let afFns: AngularFireFunctions; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + AngularFireModule.initializeApp(COMMON_CONFIG, rando()), + AngularFireFunctionsModule + ], + providers: [ + { provide: USE_EMULATOR, useValue: ['localhost', 9999] }, + { provide: REGION, useValue: 'us-central1' }, + ] + }); + + afFns = TestBed.inject(AngularFireFunctions); + }); + + it('should exist', () => { + expect(afFns instanceof AngularFireFunctions).toBe(true); + }); + + it('should have the Firebase Functions instance', () => { + expect(afFns.useFunctionsEmulator).toBeDefined(); + }); + +}); + +describe('AngularFireFunctions with different app', () => { + let afFns: AngularFireFunctions; + let firebaseAppName: string; + + beforeEach(() => { + firebaseAppName = rando(); + TestBed.configureTestingModule({ + imports: [ + AngularFireModule.initializeApp(COMMON_CONFIG, rando()), + AngularFireFunctionsModule + ], + providers: [ + { provide: FIREBASE_APP_NAME, useValue: firebaseAppName }, + { provide: FIREBASE_OPTIONS, useValue: COMMON_CONFIG }, + { provide: USE_EMULATOR, useValue: ['localhost', 9999] }, + { provide: REGION, useValue: 'us-central1' }, + ] + }); + + afFns = TestBed.inject(AngularFireFunctions); + }); + + describe('', () => { + + it('should be an AngularFireAuth type', () => { + expect(afFns instanceof AngularFireFunctions).toEqual(true); + }); + + it('should have the Firebase Functions instance', () => { + expect(afFns.useFunctionsEmulator).toBeDefined(); + }); + + }); + +}); diff --git a/src/compat/functions/functions.ts b/src/compat/functions/functions.ts new file mode 100644 index 000000000..b21b31df3 --- /dev/null +++ b/src/compat/functions/functions.ts @@ -0,0 +1,74 @@ +import { Inject, Injectable, InjectionToken, NgZone, Optional } from '@angular/core'; +import { ÉĩAngularFireSchedulers } from '@angular/fire'; +import { AppCheckInstances } from '@angular/fire/app-check'; +import { ÉĩPromiseProxy, ÉĩapplyMixins, ÉĩlazySDKProxy } from '@angular/fire/compat'; +import { FIREBASE_APP_NAME, FIREBASE_OPTIONS, ÉĩcacheInstance, ÉĩfirebaseAppFactory } from '@angular/fire/compat'; +import { HttpsCallableOptions } from '@firebase/functions-types'; +import { FirebaseOptions } from 'firebase/app'; +import firebase from 'firebase/compat/app'; +import { Observable, from, of } from 'rxjs'; +import { map, observeOn, shareReplay, switchMap } from 'rxjs/operators'; +import { proxyPolyfillCompat } from './base'; + +export const ORIGIN = new InjectionToken('angularfire2.functions.origin'); +export const REGION = new InjectionToken('angularfire2.functions.region'); + +type UseEmulatorArguments = Parameters; +export const USE_EMULATOR = new InjectionToken('angularfire2.functions.use-emulator'); + +// override httpsCallable for compatibility with 5.x + +export interface AngularFireFunctions extends Omit<ÉĩPromiseProxy, 'httpsCallable'> { +} + +@Injectable({ + providedIn: 'any' +}) +export class AngularFireFunctions { + + public readonly httpsCallable: (name: string, options?: HttpsCallableOptions) => (data: T) => Observable; + + constructor( + @Inject(FIREBASE_OPTIONS) options: FirebaseOptions, + @Optional() @Inject(FIREBASE_APP_NAME) name: string | null | undefined, + zone: NgZone, + schedulers: ÉĩAngularFireSchedulers, + @Optional() @Inject(REGION) region: string | null, + @Optional() @Inject(ORIGIN) origin: string | null, + @Optional() @Inject(USE_EMULATOR) _useEmulator: any, // can't use the tuple here + @Optional() _appCheckInstances: AppCheckInstances, + ) { + const useEmulator: UseEmulatorArguments | null = _useEmulator; + + const functions = of(undefined).pipe( + observeOn(schedulers.outsideAngular), + switchMap(() => import('firebase/compat/functions')), + map(() => ÉĩfirebaseAppFactory(options, zone, name)), + map(app => ÉĩcacheInstance(`${app.name}.functions.${region || origin}`, 'AngularFireFunctions', app.name, () => { + let functions: firebase.functions.Functions; + if (region && origin) { + throw new Error('REGION and ORIGIN can\'t be used at the same time.'); + } + functions = app.functions(region || origin || undefined); + if (useEmulator) { + functions.useEmulator(...useEmulator); + } + return functions; + }, [region, origin, useEmulator])), + shareReplay({ bufferSize: 1, refCount: false }) + ); + + this.httpsCallable = (name: string, options?: HttpsCallableOptions) => + (data: T) => from(functions).pipe( + observeOn(schedulers.insideAngular), + switchMap(functions => functions.httpsCallable(name, options)(data)), + map(r => r.data as R) + ); + + return ÉĩlazySDKProxy(this, functions, zone); + + } + +} + +ÉĩapplyMixins(AngularFireFunctions, [proxyPolyfillCompat]); diff --git a/src/compat/functions/ng-package.json b/src/compat/functions/ng-package.json new file mode 100644 index 000000000..e5e5ccdfc --- /dev/null +++ b/src/compat/functions/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/compat/functions/package.json b/src/compat/functions/package.json new file mode 100644 index 000000000..8dce418a5 --- /dev/null +++ b/src/compat/functions/package.json @@ -0,0 +1,3 @@ +{ + "$schema": "../../node_modules/ng-packagr/package.schema.json" +} diff --git a/src/compat/functions/public_api.ts b/src/compat/functions/public_api.ts new file mode 100644 index 000000000..2ce2fab44 --- /dev/null +++ b/src/compat/functions/public_api.ts @@ -0,0 +1,5 @@ + +import 'firebase/compat/functions'; // removed in build process when not UMD + +export * from './functions'; +export * from './functions.module'; diff --git a/src/compat/messaging/base.ts b/src/compat/messaging/base.ts new file mode 100644 index 000000000..fc1c529f6 --- /dev/null +++ b/src/compat/messaging/base.ts @@ -0,0 +1,8 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +// Export a null object with the same keys as firebase/compat/messaging, so Proxy can work with proxy-polyfill in Internet Explorer +export const proxyPolyfillCompat = { + deleteToken: null, + getToken: null, + onMessage: null, + onBackgroundMessage: null, +}; diff --git a/src/compat/messaging/messaging.module.ts b/src/compat/messaging/messaging.module.ts new file mode 100644 index 000000000..6912c4992 --- /dev/null +++ b/src/compat/messaging/messaging.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { VERSION } from '@angular/fire'; +import firebase from 'firebase/compat/app'; +import { AngularFireMessaging } from './messaging'; + +@NgModule({ + providers: [ AngularFireMessaging ] +}) +export class AngularFireMessagingModule { + constructor() { + firebase.registerVersion('angularfire', VERSION.full, 'fcm-compat'); + } +} diff --git a/src/compat/messaging/messaging.spec.ts b/src/compat/messaging/messaging.spec.ts new file mode 100644 index 000000000..ddd616d98 --- /dev/null +++ b/src/compat/messaging/messaging.spec.ts @@ -0,0 +1,63 @@ +import { TestBed } from '@angular/core/testing'; +import { AngularFireModule, FIREBASE_APP_NAME, FIREBASE_OPTIONS } from '@angular/fire/compat'; +import { AngularFireMessaging, AngularFireMessagingModule } from '@angular/fire/compat/messaging'; +import { COMMON_CONFIG } from '../../../src/test-config'; +import { rando } from '../../../src/utils'; + +describe('AngularFireMessaging', () => { + let afm: AngularFireMessaging; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + AngularFireModule.initializeApp(COMMON_CONFIG, rando()), + AngularFireMessagingModule + ] + }); + + afm = TestBed.inject(AngularFireMessaging); + }); + + it('should be exist', () => { + expect(afm instanceof AngularFireMessaging).toBe(true); + }); + + it('should have the FCM instance', () => { + expect(afm.deleteToken).toBeDefined(); + }); + +}); + +const FIREBASE_APP_NAME_TOO = (Math.random() + 1).toString(36).substring(7); + +describe('AngularFireMessaging with different app', () => { + let afm: AngularFireMessaging; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + AngularFireModule.initializeApp(COMMON_CONFIG, rando()), + AngularFireMessagingModule + ], + providers: [ + { provide: FIREBASE_APP_NAME, useValue: FIREBASE_APP_NAME_TOO }, + { provide: FIREBASE_OPTIONS, useValue: COMMON_CONFIG } + ] + }); + + afm = TestBed.inject(AngularFireMessaging); + }); + + describe('', () => { + + it('should be an AngularFireMessaging type', () => { + expect(afm instanceof AngularFireMessaging).toEqual(true); + }); + + it('should have the FCM instance', () => { + expect(afm.deleteToken).toBeDefined(); + }); + + }); + +}); diff --git a/src/compat/messaging/messaging.ts b/src/compat/messaging/messaging.ts new file mode 100644 index 000000000..301647b02 --- /dev/null +++ b/src/compat/messaging/messaging.ts @@ -0,0 +1,125 @@ +import { Inject, Injectable, InjectionToken, NgZone, Optional, PLATFORM_ID } from '@angular/core'; +import { ÉĩAngularFireSchedulers } from '@angular/fire'; +import { ÉĩPromiseProxy, ÉĩapplyMixins, ÉĩlazySDKProxy } from '@angular/fire/compat'; +import { FIREBASE_APP_NAME, FIREBASE_OPTIONS, ÉĩcacheInstance, ÉĩfirebaseAppFactory } from '@angular/fire/compat'; +import { FirebaseOptions } from 'firebase/app'; +import firebase from 'firebase/compat/app'; +import { isSupported } from 'firebase/messaging'; +import { EMPTY, Observable, concat, of } from 'rxjs'; +import { catchError, defaultIfEmpty, map, mergeMap, observeOn, shareReplay, subscribeOn, switchMap, switchMapTo } from 'rxjs/operators'; +import { proxyPolyfillCompat } from './base'; + +export const VAPID_KEY = new InjectionToken('angularfire2.messaging.vapid-key'); +export const SERVICE_WORKER = new InjectionToken>('angularfire2.messaging.service-worker-registeration'); + + +export interface AngularFireMessaging extends Omit<ÉĩPromiseProxy, 'deleteToken' | 'getToken' | 'requestPermission'> { +} + +@Injectable({ + providedIn: 'any' +}) +export class AngularFireMessaging { + + public readonly requestPermission: Observable; + public readonly getToken: Observable; + public readonly tokenChanges: Observable; + public readonly messages: Observable; + public readonly requestToken: Observable; + public readonly deleteToken: (token: string) => Observable; + + constructor( + @Inject(FIREBASE_OPTIONS) options: FirebaseOptions, + @Optional() @Inject(FIREBASE_APP_NAME) name: string | null | undefined, + + @Inject(PLATFORM_ID) platformId: object, + zone: NgZone, + schedulers: ÉĩAngularFireSchedulers, + @Optional() @Inject(VAPID_KEY) vapidKey: string|null, + @Optional() @Inject(SERVICE_WORKER) _serviceWorker: any, + ) { + const serviceWorker: Promise | null = _serviceWorker; + + const messaging = of(undefined).pipe( + subscribeOn(schedulers.outsideAngular), + observeOn(schedulers.insideAngular), + switchMap(isSupported), + switchMap(supported => supported ? import('firebase/compat/messaging') : EMPTY), + map(() => ÉĩfirebaseAppFactory(options, zone, name)), + switchMap(app => ÉĩcacheInstance(`${app.name}.messaging`, 'AngularFireMessaging', app.name, () => { + return of(app.messaging()); + }, [])), + shareReplay({ bufferSize: 1, refCount: false }) + ); + + + this.requestPermission = messaging.pipe( + subscribeOn(schedulers.outsideAngular), + observeOn(schedulers.insideAngular), + switchMap(() => Notification.requestPermission()) + ); + + this.getToken = messaging.pipe( + subscribeOn(schedulers.outsideAngular), + observeOn(schedulers.insideAngular), + switchMap(async messaging => { + if (Notification.permission === 'granted') { + // eslint-disable-next-line @typescript-eslint/no-misused-promises + const serviceWorkerRegistration = serviceWorker ? await serviceWorker : null; + return await messaging.getToken({ vapidKey, serviceWorkerRegistration }); + } else { + return null; + } + }) + ); + + const notificationPermission$ = new Observable(emitter => { + navigator.permissions.query({ name: 'notifications' }).then(notificationPerm => { + notificationPerm.onchange = () => emitter.next(); + }); + }); + + + const tokenChange$ = messaging.pipe( + subscribeOn(schedulers.outsideAngular), + observeOn(schedulers.insideAngular), + switchMapTo(notificationPermission$), + switchMapTo(this.getToken) + ); + + this.tokenChanges = messaging.pipe( + subscribeOn(schedulers.outsideAngular), + observeOn(schedulers.insideAngular), + switchMap(() => concat(this.getToken, tokenChange$)) + ); + + + this.messages = messaging.pipe( + subscribeOn(schedulers.outsideAngular), + observeOn(schedulers.insideAngular), + switchMap(messaging => new Observable(emitter => + messaging.onMessage(emitter) + )), + ); + + this.requestToken = messaging.pipe( + subscribeOn(schedulers.outsideAngular), + observeOn(schedulers.insideAngular), + switchMap(() => this.requestPermission), + catchError(() => of(null)), + mergeMap(() => this.tokenChanges) + ); + + this.deleteToken = () => messaging.pipe( + subscribeOn(schedulers.outsideAngular), + observeOn(schedulers.insideAngular), + switchMap(messaging => messaging.deleteToken()), + defaultIfEmpty(false) + ); + + return ÉĩlazySDKProxy(this, messaging, zone); + } + +} + +ÉĩapplyMixins(AngularFireMessaging, [proxyPolyfillCompat]); diff --git a/src/compat/messaging/ng-package.json b/src/compat/messaging/ng-package.json new file mode 100644 index 000000000..e5e5ccdfc --- /dev/null +++ b/src/compat/messaging/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/compat/messaging/package.json b/src/compat/messaging/package.json new file mode 100644 index 000000000..8dce418a5 --- /dev/null +++ b/src/compat/messaging/package.json @@ -0,0 +1,3 @@ +{ + "$schema": "../../node_modules/ng-packagr/package.schema.json" +} diff --git a/src/compat/messaging/public_api.ts b/src/compat/messaging/public_api.ts new file mode 100644 index 000000000..90df6c649 --- /dev/null +++ b/src/compat/messaging/public_api.ts @@ -0,0 +1,2 @@ +export * from './messaging'; +export * from './messaging.module'; diff --git a/src/compat/ng-package.json b/src/compat/ng-package.json new file mode 100644 index 000000000..d3032da35 --- /dev/null +++ b/src/compat/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/compat/package.json b/src/compat/package.json new file mode 100644 index 000000000..8dce418a5 --- /dev/null +++ b/src/compat/package.json @@ -0,0 +1,3 @@ +{ + "$schema": "../../node_modules/ng-packagr/package.schema.json" +} diff --git a/src/compat/performance/base.ts b/src/compat/performance/base.ts new file mode 100644 index 000000000..29e4cf620 --- /dev/null +++ b/src/compat/performance/base.ts @@ -0,0 +1,8 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +// Export a null object with the same keys as firebase/compat/performance, so Proxy can work with proxy-polyfill in Internet Explorer +export const proxyPolyfillCompat = { + app: null, + trace: null, + instrumentationEnabled: null, + dataCollectionEnabled: null, +}; diff --git a/src/compat/performance/ng-package.json b/src/compat/performance/ng-package.json new file mode 100644 index 000000000..e5e5ccdfc --- /dev/null +++ b/src/compat/performance/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/compat/performance/package.json b/src/compat/performance/package.json new file mode 100644 index 000000000..8dce418a5 --- /dev/null +++ b/src/compat/performance/package.json @@ -0,0 +1,3 @@ +{ + "$schema": "../../node_modules/ng-packagr/package.schema.json" +} diff --git a/src/compat/performance/performance.module.ts b/src/compat/performance/performance.module.ts new file mode 100644 index 000000000..5caf2eb97 --- /dev/null +++ b/src/compat/performance/performance.module.ts @@ -0,0 +1,19 @@ +import { NgModule, Optional } from '@angular/core'; +import { VERSION } from '@angular/fire'; +import firebase from 'firebase/compat/app'; +import { AngularFirePerformance } from './performance'; +import { PerformanceMonitoringService } from './performance.service'; + +@NgModule({ + providers: [ AngularFirePerformance ] +}) +export class AngularFirePerformanceModule { + constructor( + perf: AngularFirePerformance, + @Optional() _: PerformanceMonitoringService + ) { + firebase.registerVersion('angularfire', VERSION.full, 'perf-compat'); + // call anything here to get perf loading + perf.dataCollectionEnabled.then(() => undefined); + } +} diff --git a/src/performance/performance.service.ts b/src/compat/performance/performance.service.ts similarity index 69% rename from src/performance/performance.service.ts rename to src/compat/performance/performance.service.ts index 4f1c6bc2f..777ed0bc5 100644 --- a/src/performance/performance.service.ts +++ b/src/compat/performance/performance.service.ts @@ -2,29 +2,17 @@ import { ApplicationRef, Injectable, OnDestroy } from '@angular/core'; import { Subscription } from 'rxjs'; import { first, tap } from 'rxjs/operators'; -const IS_STABLE_START_MARK = '_isStableStart'; +const IS_STABLE_START_MARK = 'Zone'; const IS_STABLE_END_MARK = '_isStableEnd'; -function markStarts() { - if (typeof(window) !== 'undefined' && window.performance) { - window.performance.mark(IS_STABLE_START_MARK); - return true; - } else { - return false; - } -} - -const started = markStarts(); - -@Injectable({ - providedIn: 'any' -}) +@Injectable() export class PerformanceMonitoringService implements OnDestroy { private disposable: Subscription|undefined; constructor(appRef: ApplicationRef) { - if (started) { + + if (typeof window !== 'undefined' && window.performance?.mark) { this.disposable = appRef.isStable.pipe( first(it => it), tap(() => { diff --git a/src/compat/performance/performance.spec.ts b/src/compat/performance/performance.spec.ts new file mode 100644 index 000000000..dcd36d340 --- /dev/null +++ b/src/compat/performance/performance.spec.ts @@ -0,0 +1,29 @@ +import { TestBed } from '@angular/core/testing'; +import { AngularFireModule } from '@angular/fire/compat'; +import { AngularFirePerformance, AngularFirePerformanceModule } from '@angular/fire/compat/performance'; +import { COMMON_CONFIG } from '../../../src/test-config'; + +describe('AngularFirePerformance', () => { + let afp: AngularFirePerformance; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + // NOTE: You must use the [DEFAULT] app instance + // for these tests to work. + AngularFireModule.initializeApp(COMMON_CONFIG), + AngularFirePerformanceModule + ] + }); + afp = TestBed.inject(AngularFirePerformance); + }); + + it('should exist', () => { + expect(afp instanceof AngularFirePerformance).toBe(true); + }); + + it('should have the Performance instance', () => { + expect(afp.dataCollectionEnabled).toBeDefined(); + }); + +}); diff --git a/src/compat/performance/performance.ts b/src/compat/performance/performance.ts new file mode 100644 index 000000000..49e1a05e4 --- /dev/null +++ b/src/compat/performance/performance.ts @@ -0,0 +1,147 @@ +import { isPlatformBrowser } from '@angular/common'; +import { Inject, Injectable, InjectionToken, NgZone, Optional, PLATFORM_ID } from '@angular/core'; +import { ÉĩPromiseProxy, ÉĩapplyMixins, ÉĩcacheInstance, ÉĩlazySDKProxy } from '@angular/fire/compat'; +import { FirebaseApp } from '@angular/fire/compat'; +import firebase from 'firebase/compat/app'; +import { EMPTY, Observable, Subscription, of } from 'rxjs'; +import { map, shareReplay, switchMap, tap } from 'rxjs/operators'; +import { proxyPolyfillCompat } from './base'; + +export const INSTRUMENTATION_ENABLED = new InjectionToken('angularfire2.performance.instrumentationEnabled'); +export const DATA_COLLECTION_ENABLED = new InjectionToken('angularfire2.performance.dataCollectionEnabled'); + + +export interface AngularFirePerformance extends ÉĩPromiseProxy { +} + +@Injectable({ + providedIn: 'any' +}) +export class AngularFirePerformance { + + private readonly performance: Observable; + + constructor( + app: FirebaseApp, + @Optional() @Inject(INSTRUMENTATION_ENABLED) instrumentationEnabled: boolean | null, + @Optional() @Inject(DATA_COLLECTION_ENABLED) dataCollectionEnabled: boolean | null, + private zone: NgZone, + + @Inject(PLATFORM_ID) platformId: object + ) { + + this.performance = of(undefined).pipe( + switchMap(() => isPlatformBrowser(platformId) ? zone.runOutsideAngular(() => import('firebase/compat/performance')) : EMPTY), + map(() => ÉĩcacheInstance(`performance`, 'AngularFirePerformance', app.name, () => { + const performance = zone.runOutsideAngular(() => app.performance()); + if (instrumentationEnabled === false) { + performance.instrumentationEnabled = false; + } + if (dataCollectionEnabled === false) { + performance.dataCollectionEnabled = false; + } + return performance; + }, [instrumentationEnabled, dataCollectionEnabled])), + shareReplay({ bufferSize: 1, refCount: false }) + ); + + return ÉĩlazySDKProxy(this, this.performance, zone); + + } + +} + +const trace$ = (traceId: string) => { + + if (typeof window !== 'undefined' && window.performance?.mark) { + const entries = window.performance.getEntriesByName(traceId, 'measure') || []; + const startMarkName = `_${traceId}Start[${entries.length}]`; + const endMarkName = `_${traceId}End[${entries.length}]`; + return new Observable(emitter => { + window.performance.mark(startMarkName); + emitter.next(); + return { + unsubscribe: () => { + window.performance.mark(endMarkName); + window.performance.measure(traceId, startMarkName, endMarkName); + } + }; + }); + } else { + return EMPTY; + } +}; + +export const traceUntil = ( + name: string, + test: (a: T) => boolean, + options?: { orComplete?: boolean } +) => (source$: Observable) => new Observable(subscriber => { + const traceSubscription = trace$(name).subscribe(); + return source$.pipe( + tap( + a => test(a) && traceSubscription.unsubscribe(), + () => undefined, + () => options && options.orComplete && traceSubscription.unsubscribe() + ) + ).subscribe(subscriber); +}); + +export const traceWhile = ( + name: string, + test: (a: T) => boolean, + options?: { orComplete?: boolean } +) => (source$: Observable) => new Observable(subscriber => { + let traceSubscription: Subscription | undefined; + return source$.pipe( + tap( + a => { + if (test(a)) { + traceSubscription = traceSubscription || trace$(name).subscribe(); + } else { + if (traceSubscription) { + traceSubscription.unsubscribe(); + } + + traceSubscription = undefined; + } + }, + () => undefined, + () => options?.orComplete && traceSubscription?.unsubscribe() + ) + ).subscribe(subscriber); +}); + +export const traceUntilComplete = (name: string) => (source$: Observable) => new Observable(subscriber => { + const traceSubscription = trace$(name).subscribe(); + return source$.pipe( + tap( + () => undefined, + () => undefined, + () => traceSubscription.unsubscribe() + ) + ).subscribe(subscriber); +}); + +export const traceUntilFirst = (name: string) => (source$: Observable) => new Observable(subscriber => { + const traceSubscription = trace$(name).subscribe(); + return source$.pipe( + tap( + () => traceSubscription.unsubscribe(), + () => undefined, + () => undefined ) + ).subscribe(subscriber); +}); + +export const trace = (name: string) => (source$: Observable) => new Observable(subscriber => { + const traceSubscription = trace$(name).subscribe(); + return source$.pipe( + tap( + () => traceSubscription.unsubscribe(), + () => undefined, + () => traceSubscription.unsubscribe() + ) + ).subscribe(subscriber); +}); + +ÉĩapplyMixins(AngularFirePerformance, [proxyPolyfillCompat]); diff --git a/src/compat/performance/public_api.ts b/src/compat/performance/public_api.ts new file mode 100644 index 000000000..b6dc28449 --- /dev/null +++ b/src/compat/performance/public_api.ts @@ -0,0 +1,3 @@ +export * from './performance'; +export * from './performance.module'; +export * from './performance.service'; diff --git a/src/compat/proxy.ts b/src/compat/proxy.ts new file mode 100644 index 000000000..1b70b84ce --- /dev/null +++ b/src/compat/proxy.ts @@ -0,0 +1,87 @@ +import { NgZone } from '@angular/core'; +import { Observable } from 'rxjs'; + +type MyFunction = (...args: any[]) => any; +type FunctionPropertyNames = { [K in keyof T]: T[K] extends MyFunction ? K : never }[keyof T]; +type ReturnTypeOrNever = T extends MyFunction ? ReturnType : never; +type ParametersOrNever = T extends MyFunction ? Parameters : never; +type PromiseReturningFunctionPropertyNames = { + [K in FunctionPropertyNames]: ReturnTypeOrNever extends Promise ? K : never +}[FunctionPropertyNames]; +type NonPromiseReturningFunctionPropertyNames = { + [K in FunctionPropertyNames]: ReturnTypeOrNever extends Promise ? never : K +}[FunctionPropertyNames]; +type NonFunctionPropertyNames = { [K in keyof T]: T[K] extends MyFunction ? never : K }[keyof T]; + +export type ÉĩPromiseProxy = { [K in NonFunctionPropertyNames]: Promise } & + { [K in NonPromiseReturningFunctionPropertyNames]: (...args: ParametersOrNever) => Promise> } & + { [K in PromiseReturningFunctionPropertyNames]: T[K] }; + +// DEBUG quick debugger function for inline logging that typescript doesn't complain about +// wrote it for debugging the ÉĩlazySDKProxy, commenting out for now; should consider exposing a +// verbose mode for AngularFire in a future release that uses something like this in multiple places +// usage: () => log('something') || returnValue +// const log = (...args: any[]): false => { console.log(...args); return false } + +// The problem here are things like ngOnDestroy are missing, then triggering the service +// rather than dig too far; I'm capturing these as I go. +const noopFunctions = ['ngOnDestroy']; + +// INVESTIGATE should we make the Proxy revokable and do some cleanup? +// right now it's fairly simple but I'm sure this will grow in complexity +export const ÉĩlazySDKProxy = (klass: any, observable: Observable, zone: NgZone, options: { + spy?: { + get?: ((name: string, it: any) => void), + apply?: ((name: string, args: any[], it: any) => void) + } +} = {}) => { + return new Proxy(klass, { + get: (_, name: string) => zone.runOutsideAngular(() => { + if (klass[name]) { + if (options?.spy?.get) { + options.spy.get(name, klass[name]); + } + return klass[name]; + } + if (noopFunctions.includes(name)) { + return () => undefined; + } + const promise = observable.toPromise().then(mod => { + const ret = mod?.[name]; + // TODO move to proper type guards + if (typeof ret === 'function') { + return ret.bind(mod); + } else if (ret?.then) { + return ret.then((res: any) => zone.run(() => res)); + } else { + return zone.run(() => ret); + } + }); + // recurse the proxy + return new Proxy(() => undefined, { + get: (_, name) => promise[name], + // TODO handle callbacks as transparently as I can + apply: (self, _, args) => promise.then(it => { + const res = it?.(...args); + if (options?.spy?.apply) { + options.spy.apply(name, args, res); + } + return res; + }) + } + ); + }) + }); +}; + +export const ÉĩapplyMixins = (derivedCtor: any, constructors: any[]) => { + constructors.forEach((baseCtor) => { + Object.getOwnPropertyNames(baseCtor.prototype || baseCtor).forEach((name) => { + Object.defineProperty( + derivedCtor.prototype, + name, + Object.getOwnPropertyDescriptor(baseCtor.prototype || baseCtor, name) + ); + }); + }); +}; diff --git a/src/compat/public_api.ts b/src/compat/public_api.ts new file mode 100644 index 000000000..3d5ea5ea3 --- /dev/null +++ b/src/compat/public_api.ts @@ -0,0 +1,4 @@ +export * from './proxy'; +export * from './firebase.app'; +export * from './firebase.app.module'; +export * from './cache'; diff --git a/src/compat/remote-config/base.ts b/src/compat/remote-config/base.ts new file mode 100644 index 000000000..376176aa3 --- /dev/null +++ b/src/compat/remote-config/base.ts @@ -0,0 +1,19 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +// Export a null object with the same keys as firebase/compat/remote-config, so Proxy can work with proxy-polyfill in Internet Explorer +export const proxyPolyfillCompat = { + app: null, + settings: null, + defaultConfig: null, + fetchTimeMillis: null, + lastFetchStatus: null, + activate: null, + ensureInitialized: null, + fetch: null, + fetchAndActivate: null, + getAll: null, + getBoolean: null, + getNumber: null, + getString: null, + getValue: null, + setLogLevel: null, +}; diff --git a/src/remote-config/index.ts b/src/compat/remote-config/index.ts similarity index 100% rename from src/remote-config/index.ts rename to src/compat/remote-config/index.ts diff --git a/src/compat/remote-config/interfaces.ts b/src/compat/remote-config/interfaces.ts new file mode 100644 index 000000000..28927ef5e --- /dev/null +++ b/src/compat/remote-config/interfaces.ts @@ -0,0 +1,3 @@ +import firebase from 'firebase/compat/app'; + +export type Settings = firebase.remoteConfig.Settings; diff --git a/src/compat/remote-config/ng-package.json b/src/compat/remote-config/ng-package.json new file mode 100644 index 000000000..e5e5ccdfc --- /dev/null +++ b/src/compat/remote-config/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/compat/remote-config/package.json b/src/compat/remote-config/package.json new file mode 100644 index 000000000..8dce418a5 --- /dev/null +++ b/src/compat/remote-config/package.json @@ -0,0 +1,3 @@ +{ + "$schema": "../../node_modules/ng-packagr/package.schema.json" +} diff --git a/src/compat/remote-config/public_api.ts b/src/compat/remote-config/public_api.ts new file mode 100644 index 000000000..f3239f665 --- /dev/null +++ b/src/compat/remote-config/public_api.ts @@ -0,0 +1,2 @@ +export * from './remote-config'; +export * from './remote-config.module'; diff --git a/src/compat/remote-config/remote-config.module.ts b/src/compat/remote-config/remote-config.module.ts new file mode 100644 index 000000000..485358dc7 --- /dev/null +++ b/src/compat/remote-config/remote-config.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { VERSION } from '@angular/fire'; +import firebase from 'firebase/compat/app'; +import { AngularFireRemoteConfig } from './remote-config'; + +@NgModule({ + providers: [ AngularFireRemoteConfig ] +}) +export class AngularFireRemoteConfigModule { + constructor() { + firebase.registerVersion('angularfire', VERSION.full, 'rc-compat'); + } +} diff --git a/src/compat/remote-config/remote-config.spec.ts b/src/compat/remote-config/remote-config.spec.ts new file mode 100644 index 000000000..f585379d7 --- /dev/null +++ b/src/compat/remote-config/remote-config.spec.ts @@ -0,0 +1,65 @@ +import { TestBed } from '@angular/core/testing'; +import { AngularFireModule, FIREBASE_APP_NAME, FIREBASE_OPTIONS } from '@angular/fire/compat'; +import { AngularFireRemoteConfig, AngularFireRemoteConfigModule, DEFAULTS, SETTINGS } from '@angular/fire/compat/remote-config'; +import { COMMON_CONFIG } from '../../../src/test-config'; +import { rando } from '../../../src/utils'; + +describe('AngularFireRemoteConfig', () => { + let rc: AngularFireRemoteConfig; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + AngularFireModule.initializeApp(COMMON_CONFIG, rando()), + AngularFireRemoteConfigModule + ] + }); + + rc = TestBed.inject(AngularFireRemoteConfig); + }); + + it('should be exist', () => { + expect(rc instanceof AngularFireRemoteConfig).toBe(true); + }); + + it('should have the Firebase Functions instance', () => { + expect(rc.getValue).toBeDefined(); + }); + +}); + +const FIREBASE_APP_NAME_TOO = (Math.random() + 1).toString(36).substring(7); + +describe('AngularFireRemoteConfig with different app', () => { + let rc: AngularFireRemoteConfig; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + AngularFireModule.initializeApp(COMMON_CONFIG, rando()), + AngularFireRemoteConfigModule + ], + providers: [ + { provide: FIREBASE_APP_NAME, useValue: FIREBASE_APP_NAME_TOO }, + { provide: FIREBASE_OPTIONS, useValue: COMMON_CONFIG }, + { provide: SETTINGS, useValue: {} }, + { provide: DEFAULTS, useValue: {} } + ] + }); + + rc = TestBed.inject(AngularFireRemoteConfig); + }); + + describe('', () => { + + it('should be an AngularFireAuth type', () => { + expect(rc instanceof AngularFireRemoteConfig).toEqual(true); + }); + + it('should have the Firebase Functions instance', () => { + expect(rc.getValue).toBeDefined(); + }); + + }); + +}); diff --git a/src/compat/remote-config/remote-config.ts b/src/compat/remote-config/remote-config.ts new file mode 100644 index 000000000..5e34870fe --- /dev/null +++ b/src/compat/remote-config/remote-config.ts @@ -0,0 +1,302 @@ +import { EnvironmentInjector, Inject, Injectable, InjectionToken, NgZone, Optional, inject } from '@angular/core'; +import { pendingUntilEvent } from '@angular/core/rxjs-interop'; +import { ÉĩAngularFireSchedulers } from '@angular/fire'; +import { ÉĩPromiseProxy, ÉĩapplyMixins, ÉĩlazySDKProxy } from '@angular/fire/compat'; +import { FIREBASE_APP_NAME, FIREBASE_OPTIONS, ÉĩcacheInstance, ÉĩfirebaseAppFactory } from '@angular/fire/compat'; +import { FirebaseOptions } from 'firebase/app'; +import firebase from 'firebase/compat/app'; +import { isSupported } from 'firebase/remote-config'; +import { EMPTY, MonoTypeOperatorFunction, Observable, OperatorFunction, concat, of, pipe } from 'rxjs'; +import { + debounceTime, + distinctUntilChanged, + filter, + groupBy, + map, + mergeMap, + observeOn, + scan, + shareReplay, + startWith, + switchMap, + withLatestFrom +} from 'rxjs/operators'; +import { proxyPolyfillCompat } from './base'; +import { Settings } from './interfaces'; + +export type ConfigTemplate = Record; + +export const SETTINGS = new InjectionToken('angularfire2.remoteConfig.settings'); +export const DEFAULTS = new InjectionToken('angularfire2.remoteConfig.defaultConfig'); + + +export interface AngularFireRemoteConfig extends ÉĩPromiseProxy { +} + +const AS_TO_FN = { strings: 'asString', numbers: 'asNumber', booleans: 'asBoolean' }; +const STATIC_VALUES = { numbers: 0, booleans: false, strings: undefined }; + +// TODO look into the types here, I don't like the anys +const proxyAll = (observable: Observable, as: 'numbers' | 'booleans' | 'strings') => new Proxy( + observable.pipe(mapToObject(as as any)), { + get: (self, name: string) => self[name] || observable.pipe( + map(all => all.find(p => p.key === name)), + map(param => param ? param[AS_TO_FN[as]]() : STATIC_VALUES[as]), + distinctUntilChanged() + ) + } +) as any; + +// TODO export as implements Partial<...> so minor doesn't break us +export class Value implements firebase.remoteConfig.Value { + asBoolean() { + return ['1', 'true', 't', 'y', 'yes', 'on'].includes(this._value.toLowerCase()); + } + + asString() { + return this._value; + } + + asNumber() { + return Number(this._value) || 0; + } + + getSource() { + return this._source; + } + + constructor(public _source: firebase.remoteConfig.ValueSource, public _value: string) { + } +} + +// SEMVER use ConstructorParameters when we can support Typescript 3.6 +export class Parameter extends Value { + constructor(public key: string, public fetchTimeMillis: number, source: firebase.remoteConfig.ValueSource, value: string) { + super(source, value); + } +} + +// If it's a Parameter array, test any, else test the individual Parameter +const filterTest = (fn: (param: Parameter) => boolean) => filter(it => Array.isArray(it) ? it.some(fn) : fn(it)); + +// Allow the user to bypass the default values and wait till they get something from the server, even if it's a cached copy; +// if used in conjuntion with first() it will only fetch RC values from the server if they aren't cached locally +export const filterRemote = () => filterTest(p => p.getSource() === 'remote'); + +// filterFresh allows the developer to effectively set up a maximum cache time +export const filterFresh = (howRecentInMillis: number) => filterTest(p => p.fetchTimeMillis + howRecentInMillis >= new Date().getTime()); + + +// I ditched loading the defaults into RC and a simple map for scan since we already have our own defaults implementation. +// The idea here being that if they have a default that never loads from the server, they will be able to tell via fetchTimeMillis +// on the Parameter. Also if it doesn't come from the server it won't emit again in .changes, due to the distinctUntilChanged, +// which we can simplify to === rather than deep comparison +const scanToParametersArray = ( + remoteConfig: Observable +): OperatorFunction, Parameter[]> => pipe( + withLatestFrom(remoteConfig), + scan((existing, [all, rc]) => { + // SEMVER use "new Set" to unique once we're only targeting es6 + // at the scale we expect remote config to be at, we probably won't see a performance hit from this unoptimized uniqueness + // implementation. + // const allKeys = [...new Set([...existing.map(p => p.key), ...Object.keys(all)])]; + const allKeys = [...existing.map(p => p.key), ...Object.keys(all)].filter((v, i, a) => a.indexOf(v) === i); + return allKeys.map(key => { + const updatedValue = all[key]; + return updatedValue ? new Parameter(key, rc ? rc.fetchTimeMillis : -1, updatedValue.getSource(), updatedValue.asString()) + : existing.find(p => p.key === key); + }); + }, [] as Parameter[]) +); + + +@Injectable({ + providedIn: 'any' +}) +export class AngularFireRemoteConfig { + + readonly changes: Observable; + readonly parameters: Observable; + readonly numbers: Observable> & Record>; + readonly booleans: Observable> & Record>; + readonly strings: Observable> & Record>; + + private readonly injector = inject(EnvironmentInjector); + + constructor( + @Inject(FIREBASE_OPTIONS) options: FirebaseOptions, + @Optional() @Inject(FIREBASE_APP_NAME) name: string | null | undefined, + @Optional() @Inject(SETTINGS) settings: Settings | null, + @Optional() @Inject(DEFAULTS) defaultConfig: ConfigTemplate | null, + private zone: NgZone, + schedulers: ÉĩAngularFireSchedulers, + ) { + const remoteConfig$ = of(undefined).pipe( + observeOn(schedulers.outsideAngular), + switchMap(() => isSupported()), + switchMap(isSupported => isSupported ? import('firebase/compat/remote-config') : EMPTY), + map(() => ÉĩfirebaseAppFactory(options, zone, name)), + map(app => ÉĩcacheInstance(`${app.name}.remote-config`, 'AngularFireRemoteConfig', app.name, () => { + const rc = app.remoteConfig(); + if (settings) { + rc.settings = settings; + } + if (defaultConfig) { + rc.defaultConfig = defaultConfig; + } + return rc; + }, [settings, defaultConfig])), + startWith(undefined), + shareReplay({ bufferSize: 1, refCount: false }) + ) as Observable; + + const loadedRemoteConfig$ = remoteConfig$.pipe( + filter(rc => !!rc) + ); + + const default$: Observable> = of(Object.keys(defaultConfig || {}).reduce( + (c, k) => ({ ...c, [k]: new Value('default', defaultConfig[k].toString()) }), {} + )); + + // we should filter out the defaults we provided to RC, since we have our own implementation + // that gives us a -1 for fetchTimeMillis (so filterFresh can filter them out) + const filterOutDefaults = map, Record>(all => + Object.keys(all) + .filter(key => all[key].getSource() !== 'default') + .reduce((acc, key) => ({ ...acc, [key]: all[key] }), {}) + ); + + const existing$ = loadedRemoteConfig$.pipe( + switchMap(rc => + rc.activate() + .then(() => rc.ensureInitialized()) + .then(() => rc.getAll()) + ), + filterOutDefaults + ); + + const fresh$ = loadedRemoteConfig$.pipe( + switchMap(rc => zone.runOutsideAngular(() => + rc.fetchAndActivate() + .then(() => rc.ensureInitialized()) + .then(() => rc.getAll()) + )), + filterOutDefaults + ); + + this.parameters = concat(default$, existing$, fresh$).pipe( + scanToParametersArray(remoteConfig$), + pendingUntilEvent(this.injector), + shareReplay({ bufferSize: 1, refCount: true }) + ); + + this.changes = this.parameters.pipe( + switchMap(params => of(...params)), + groupBy(param => param.key), + mergeMap(group => group.pipe( + distinctUntilChanged() + )) + ); + + this.strings = proxyAll(this.parameters, 'strings'); + this.booleans = proxyAll(this.parameters, 'booleans'); + this.numbers = proxyAll(this.parameters, 'numbers'); + + return ÉĩlazySDKProxy(this, loadedRemoteConfig$, zone); + } + +} + + +export const budget = (interval: number): MonoTypeOperatorFunction => (source: Observable) => new Observable(observer => { + let timedOut = false; + // TODO use scheduler task rather than settimeout + const timeout = setTimeout(() => { + observer.complete(); + timedOut = true; + }, interval); + return source.subscribe({ + next(val) { + if (!timedOut) { + observer.next(val); + } + }, + error(err) { + if (!timedOut) { + clearTimeout(timeout); + observer.error(err); + } + }, + complete() { + if (!timedOut) { + clearTimeout(timeout); + observer.complete(); + } + } + }); +}); + +const typedMethod = (it: any) => { + switch (typeof it) { + case 'string': + return 'asString'; + case 'boolean': + return 'asBoolean'; + case 'number': + return 'asNumber'; + default: + return 'asString'; + } +}; + + +export function scanToObject(): OperatorFunction>; +export function scanToObject(to: 'numbers'): OperatorFunction>; +export function scanToObject(to: 'booleans'): OperatorFunction>; + +export function scanToObject(to: 'strings'): OperatorFunction>; +export function scanToObject(template: T): OperatorFunction>; +export function scanToObject(to: 'numbers' | 'booleans' | 'strings' | T = 'strings') { + return pipe( + // TODO cleanup + scan( + (c, p: Parameter) => ({ + ...c, [p.key]: typeof to === 'object' ? + p[typedMethod(to[p.key])]() : + p[AS_TO_FN[to]]() + }), + typeof to === 'object' ? + to as T & Record : + {} as Record + ), + debounceTime(1), + budget(10), + distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)) + ); +} + +export function mapToObject(): OperatorFunction>; +export function mapToObject(to: 'numbers'): OperatorFunction>; +export function mapToObject(to: 'booleans'): OperatorFunction>; + +export function mapToObject(to: 'strings'): OperatorFunction>; +export function mapToObject(template: T): + OperatorFunction>; +export function mapToObject(to: 'numbers' | 'booleans' | 'strings' | T = 'strings') { + return pipe( + // TODO this is getting a little long, cleanup + map((params: Parameter[]) => params.reduce( + (c, p) => ({ + ...c, [p.key]: typeof to === 'object' ? + p[typedMethod(to[p.key])]() : + p[AS_TO_FN[to]]() + }), + typeof to === 'object' ? + to as T & Record : + {} as Record + )), + distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)) + ); +} + +ÉĩapplyMixins(AngularFireRemoteConfig, [proxyPolyfillCompat]); diff --git a/src/compat/storage/interfaces.ts b/src/compat/storage/interfaces.ts new file mode 100644 index 000000000..12b5f5175 --- /dev/null +++ b/src/compat/storage/interfaces.ts @@ -0,0 +1,11 @@ +import firebase from 'firebase/compat/app'; + +export type UploadTask = firebase.storage.UploadTask; +export type UploadTaskSnapshot = firebase.storage.UploadTaskSnapshot; +export type UploadMetadata = firebase.storage.UploadMetadata; + +export type SettableMetadata = firebase.storage.SettableMetadata; +export type Reference = firebase.storage.Reference; +export type StringFormat = firebase.storage.StringFormat; +export type ListResult = firebase.storage.ListResult; +export type ListOptions = firebase.storage.ListOptions; diff --git a/src/compat/storage/ng-package.json b/src/compat/storage/ng-package.json new file mode 100644 index 000000000..e5e5ccdfc --- /dev/null +++ b/src/compat/storage/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/compat/storage/observable/fromTask.ts b/src/compat/storage/observable/fromTask.ts new file mode 100644 index 000000000..85f8a34e5 --- /dev/null +++ b/src/compat/storage/observable/fromTask.ts @@ -0,0 +1,39 @@ +import { Observable } from 'rxjs'; +import { debounceTime } from 'rxjs/operators'; +import { UploadTask, UploadTaskSnapshot } from '../interfaces'; + +// need to import, else the types become import('firebase/compat/app').default.storage.UploadTask +// and it no longer works w/Firebase v7 + +// Things aren't working great, I'm having to put in a lot of work-arounds for what +// appear to be Firebase JS SDK bugs https://github.com/firebase/firebase-js-sdk/issues/4158 +export function fromTask(task: UploadTask) { + return new Observable(subscriber => { + const progress = (snap: UploadTaskSnapshot) => subscriber.next(snap); + const error = e => subscriber.error(e); + const complete = () => subscriber.complete(); + // emit the current snapshot, so they don't have to wait for state_changes + // to fire next... this is stale if the task is no longer running :( + progress(task.snapshot); + const unsub = task.on('state_changed', progress); + // it turns out that neither task snapshot nor 'state_changed' fire the last + // snapshot before completion, the one with status 'success" and 100% progress + // so let's use the promise form of the task for that + task.then(snapshot => { + progress(snapshot); + complete(); + }, e => { + // TODO investigate, again this is stale, we never fire a canceled or error it seems + progress(task.snapshot); + error(e); + }); + // on's type if Function, rather than () => void, need to wrap + return function unsubscribe() { + unsub(); + }; + }).pipe( + // deal with sync emissions from first emitting `task.snapshot`, this makes sure + // that if the task is already finished we don't emit the old running state + debounceTime(0) + ); +} diff --git a/src/compat/storage/package.json b/src/compat/storage/package.json new file mode 100644 index 000000000..8dce418a5 --- /dev/null +++ b/src/compat/storage/package.json @@ -0,0 +1,3 @@ +{ + "$schema": "../../node_modules/ng-packagr/package.schema.json" +} diff --git a/src/compat/storage/pipes/storageUrl.pipe.ts b/src/compat/storage/pipes/storageUrl.pipe.ts new file mode 100644 index 000000000..29c7d01d0 --- /dev/null +++ b/src/compat/storage/pipes/storageUrl.pipe.ts @@ -0,0 +1,48 @@ +import { AsyncPipe } from '@angular/common'; +import { ChangeDetectorRef, NgModule, OnDestroy, Optional, Pipe, PipeTransform, TransferState, makeStateKey } from '@angular/core'; +import { Observable, of } from 'rxjs'; +import { tap } from 'rxjs/operators'; +import { AngularFireStorage } from '../storage'; + +/** to be used with in combination with | async */ +@Pipe({ + name: 'getDownloadURL', + pure: false, +}) +export class GetDownloadURLPipe implements PipeTransform, OnDestroy { + + private asyncPipe: AsyncPipe; + private path: string; + private downloadUrl$: Observable; + + constructor( + private storage: AngularFireStorage, + cdr: ChangeDetectorRef, + @Optional() private state: TransferState + ) { + this.asyncPipe = new AsyncPipe(cdr); + } + + transform(path: string) { + if (path !== this.path) { + this.path = path; + const key = makeStateKey(`|getDownloadURL|${path}`); + const existing = this.state?.get(key, undefined); + this.downloadUrl$ = existing ? of(existing) : this.storage.ref(path).getDownloadURL().pipe( + tap(it => this.state?.set(key, it)) + ); + } + return this.asyncPipe.transform(this.downloadUrl$); + } + + ngOnDestroy() { + this.asyncPipe.ngOnDestroy(); + } + +} + +@NgModule({ + imports: [ GetDownloadURLPipe ], + exports: [ GetDownloadURLPipe ], +}) +export class GetDownloadURLPipeModule {} diff --git a/src/compat/storage/public_api.ts b/src/compat/storage/public_api.ts new file mode 100644 index 000000000..460348fa1 --- /dev/null +++ b/src/compat/storage/public_api.ts @@ -0,0 +1,6 @@ +export * from './ref'; +export * from './storage'; +export * from './task'; +export * from './observable/fromTask'; +export * from './storage.module'; +export * from './pipes/storageUrl.pipe'; diff --git a/src/storage/ref.ts b/src/compat/storage/ref.ts similarity index 59% rename from src/storage/ref.ts rename to src/compat/storage/ref.ts index 06ef4862c..c7034efa9 100644 --- a/src/storage/ref.ts +++ b/src/compat/storage/ref.ts @@ -1,17 +1,20 @@ -import { ListResult, Reference, SettableMetadata, StringFormat, UploadMetadata } from './interfaces'; -import { AngularFireUploadTask, createUploadTask } from './task'; -import { from, Observable, of } from 'rxjs'; +import { Injector } from '@angular/core'; +import { pendingUntilEvent } from '@angular/core/rxjs-interop'; import { ÉĩAngularFireSchedulers } from '@angular/fire'; +import { Observable, from, of } from 'rxjs'; import { observeOn, switchMap } from 'rxjs/operators'; +import { ListOptions, ListResult, Reference, SettableMetadata, StringFormat, UploadMetadata } from './interfaces'; +import { AngularFireUploadTask, createUploadTask } from './task'; export interface AngularFireStorageReference { getDownloadURL(): Observable; getMetadata(): Observable; delete(): Observable; - child(path: string): any; + child(path: string): AngularFireStorageReference; updateMetadata(meta: SettableMetadata): Observable; - put(data: any, metadata?: UploadMetadata | undefined): AngularFireUploadTask; - putString(data: string, format?: string | undefined, metadata?: UploadMetadata | undefined): AngularFireUploadTask; + put(data: any, metadata?: UploadMetadata ): AngularFireUploadTask; + putString(data: string, format?: string , metadata?: UploadMetadata ): AngularFireUploadTask; + list(options?: ListOptions): Observable; listAll(): Observable; } @@ -21,22 +24,21 @@ export interface AngularFireStorageReference { */ export function createStorageRef( ref: Reference, - schedulers: ÉĩAngularFireSchedulers, - keepUnstableUntilFirst: (obs$: Observable) => Observable + injector?: Injector ): AngularFireStorageReference { return { getDownloadURL: () => of(undefined).pipe( - observeOn(schedulers.outsideAngular), + observeOn(injector.get(ÉĩAngularFireSchedulers).outsideAngular), switchMap(() => ref.getDownloadURL()), - keepUnstableUntilFirst + pendingUntilEvent(injector) ), getMetadata: () => of(undefined).pipe( - observeOn(schedulers.outsideAngular), + observeOn(injector.get(ÉĩAngularFireSchedulers).outsideAngular), switchMap(() => ref.getMetadata()), - keepUnstableUntilFirst + pendingUntilEvent(injector) ), delete: () => from(ref.delete()), - child: (path: string) => createStorageRef(ref.child(path), schedulers, keepUnstableUntilFirst), + child: (path: string) => createStorageRef(ref.child(path), injector), updateMetadata: (meta: SettableMetadata) => from(ref.updateMetadata(meta)), put: (data: any, metadata?: UploadMetadata) => { const task = ref.put(data, metadata); @@ -46,6 +48,7 @@ export function createStorageRef( const task = ref.putString(data, format, metadata); return createUploadTask(task); }, + list: (options?: ListOptions) => from(ref.list(options)), listAll: () => from(ref.listAll()) }; } diff --git a/src/compat/storage/storage.module.ts b/src/compat/storage/storage.module.ts new file mode 100644 index 000000000..890c3e394 --- /dev/null +++ b/src/compat/storage/storage.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core'; +import { VERSION } from '@angular/fire'; +import firebase from 'firebase/compat/app'; +import { GetDownloadURLPipeModule } from './pipes/storageUrl.pipe'; +import { AngularFireStorage } from './storage'; + +@NgModule({ + imports: [ GetDownloadURLPipeModule ], + exports: [ GetDownloadURLPipeModule ], + providers: [ AngularFireStorage ] +}) +export class AngularFireStorageModule { + constructor() { + firebase.registerVersion('angularfire', VERSION.full, 'gcs-compat'); + } +} diff --git a/src/compat/storage/storage.spec.ts b/src/compat/storage/storage.spec.ts new file mode 100644 index 000000000..d99c3509e --- /dev/null +++ b/src/compat/storage/storage.spec.ts @@ -0,0 +1,289 @@ +import { ChangeDetectorRef } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { AngularFireModule, FIREBASE_APP_NAME, FIREBASE_OPTIONS } from '@angular/fire/compat'; +import { AngularFireStorage, AngularFireStorageModule, AngularFireUploadTask, BUCKET, USE_EMULATOR, fromTask } from '@angular/fire/compat/storage'; +import firebase from 'firebase/compat/app'; +import { forkJoin } from 'rxjs'; +import { mergeMap, tap } from 'rxjs/operators'; +import { COMMON_CONFIG, storageEmulatorPort } from '../../test-config'; +import { rando } from '../../utils'; +import 'firebase/compat/storage'; + +if (typeof XMLHttpRequest === 'undefined') { + globalThis.XMLHttpRequest = require('xhr2'); +} + +const blobOrBuffer = (data: string, options: unknown) => { + if (typeof Blob === 'undefined') { + return Buffer.from(data, 'utf8'); + } else { + return new Blob([JSON.stringify(data)], options); + } +}; + +describe('AngularFireStorage', () => { + let afStorage: AngularFireStorage; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + AngularFireModule.initializeApp(COMMON_CONFIG, rando()), + AngularFireStorageModule, + ], + providers: [ + ChangeDetectorRef, + { provide: USE_EMULATOR, useValue: ['localhost', storageEmulatorPort] } + ] + }); + + afStorage = TestBed.inject(AngularFireStorage); + }); + + it('should exist', () => { + expect(afStorage instanceof AngularFireStorage).toBe(true); + }); + + it('should have the Firebase storage instance', () => { + expect(afStorage.storage).toBeDefined(); + }); + + it('should have an initialized Firebase app', () => { + expect(afStorage.storage.app).toBeDefined(); + }); + + describe('upload task', () => { + + it('should upload and delete a file', (done) => { + const data = { angular: 'fire' }; + const blob = blobOrBuffer(JSON.stringify(data), { type: 'application/json' }); + const ref = TestBed.runInInjectionContext(() => afStorage.ref(rando())); + const task = TestBed.runInInjectionContext(() => ref.put(blob)); + let emissionCount = 0; + let lastSnap: firebase.storage.UploadTaskSnapshot; + TestBed.runInInjectionContext(() => task.snapshotChanges()) + .subscribe( + snap => { + lastSnap = snap; + emissionCount++; + expect(snap).toBeDefined(); + }, + done.fail, + () => { + expect(lastSnap.state).toBe('success'); + expect(emissionCount).toBeGreaterThan(0); + ref.delete().subscribe(done, done.fail); + }); + }); + + it('should upload a file and observe the download url', (done) => { + const data = {angular: 'fire'}; + const blob = blobOrBuffer(JSON.stringify(data), {type: 'application/json'}); + const ref = TestBed.runInInjectionContext(() => afStorage.ref(rando())); + TestBed.runInInjectionContext(() => ref.put(blob)).then(() => { + const url$ = TestBed.runInInjectionContext(() => ref.getDownloadURL()); + url$.subscribe( + url => { + expect(url).toBeDefined(); + }, + done.fail, + () => { + ref.delete().subscribe(done, done.fail); + } + ); + }); + }); + + it('should resolve the task as a promise', (done) => { + const data = { angular: 'promise' }; + const blob = blobOrBuffer(JSON.stringify(data), { type: 'application/json' }); + const ref = TestBed.runInInjectionContext(() => afStorage.ref(rando())); + const task: AngularFireUploadTask = TestBed.runInInjectionContext(() => ref.put(blob)); + task.then(snap => { + expect(snap).toBeDefined(); + done(); + }).catch(done.fail); + }); + + it('should cancel the task', (done) => { + const data = { angular: 'promise' }; + const blob = blobOrBuffer(JSON.stringify(data), { type: 'application/json' }); + const ref = TestBed.runInInjectionContext(() => afStorage.ref(rando())); + const task: AngularFireUploadTask = TestBed.runInInjectionContext(() => ref.put(blob)); + let emissionCount = 0; + let lastSnap: firebase.storage.UploadTaskSnapshot; + TestBed.runInInjectionContext(() => task.snapshotChanges()).subscribe(snap => { + emissionCount++; + lastSnap = snap; + if (emissionCount === 1) { + task.cancel(); + } + }, () => { + // TODO investigate, this doesn't appear to work... + // https://github.com/firebase/firebase-js-sdk/issues/4158 + // expect(lastSnap.state).toEqual('canceled'); + expect(emissionCount).toEqual(1); + expect(lastSnap.state).toEqual('running'); + done(); + }, done.fail); + }); + + it('should be able to pause/resume the task', (done) => { + const data = { angular: 'promise' }; + const blob = blobOrBuffer(JSON.stringify(data), { type: 'application/json' }); + const ref = TestBed.runInInjectionContext(() => afStorage.ref(rando())); + const task: AngularFireUploadTask = TestBed.runInInjectionContext(() => ref.put(blob)); + let paused = false; + task.pause(); + TestBed.runInInjectionContext(() => task.snapshotChanges()).subscribe(snap => { + if (snap.state === 'paused') { + paused = true; + task.resume(); + } + }, done.fail, () => { + expect(paused).toBeTruthy(); + done(); + }); + }); + + it('should work with an already finished task', (done) => { + const data = {angular: 'promise'}; + const blob = blobOrBuffer(JSON.stringify(data), {type: 'application/json'}); + const ref = TestBed.runInInjectionContext(() => afStorage.storage.ref(rando())); + const task = TestBed.runInInjectionContext(() => ref.put(blob)); + let emissionCount = 0; + let lastSnap: firebase.storage.UploadTaskSnapshot; + task.then(_snap => { + fromTask(task).subscribe( + snap => { + lastSnap = snap; + emissionCount++; + expect(snap).toBeDefined(); + }, + done.fail, + () => { + expect(lastSnap.state).toBe('success'); + expect(emissionCount).toBe(1); + ref.delete().then(done, done.fail); + }); + }); + }); + + }); + + describe('reference', () => { + + it('it should upload, download, and delete', (done) => { + const data = { angular: 'fire' }; + const blob = blobOrBuffer(JSON.stringify(data), { type: 'application/json' }); + const ref = TestBed.runInInjectionContext(() => afStorage.ref(rando())); + const task = TestBed.runInInjectionContext(() => ref.put(blob)); + // Wait for the upload + forkJoin([TestBed.runInInjectionContext(() => task.snapshotChanges())]) + .pipe( + // get the url download + mergeMap(() => TestBed.runInInjectionContext(() => ref.getDownloadURL())), + // assert the URL + tap(url => expect(url).toBeDefined()), + // Delete the file + mergeMap(() => ref.delete()) + ) + // finish the test + .subscribe(done, done.fail); + }); + + it('should upload, get metadata, and delete', (done) => { + pending("Not sure why this is busted."); + const data = { angular: 'fire' }; + const blob = blobOrBuffer(JSON.stringify(data), { type: 'application/json' }); + const ref = TestBed.runInInjectionContext(() => afStorage.ref(rando())); + const task = TestBed.runInInjectionContext(() => ref.put(blob, { customMetadata: { blah: 'blah' } })); + // Wait for the upload + forkJoin([TestBed.runInInjectionContext(() => task.snapshotChanges())]) + .pipe( + // get the metadata download + mergeMap(() => TestBed.runInInjectionContext(() => ref.getMetadata())), + // assert the URL + tap(meta => expect(meta.customMetadata).toEqual({ blah: 'blah' })), + ) + // finish the test + .subscribe(done, done.fail); + }); + + }); + +}); + +describe('AngularFireStorage w/options', () => { + let afStorage: AngularFireStorage; + let firebaseAppName: string; + let storageBucket: string; + + beforeEach(() => { + firebaseAppName = rando(); + storageBucket = 'angularfire2-test2'; + TestBed.configureTestingModule({ + imports: [ + AngularFireModule.initializeApp(COMMON_CONFIG, rando()), + AngularFireStorageModule + ], + providers: [ + { provide: FIREBASE_APP_NAME, useValue: firebaseAppName }, + { provide: FIREBASE_OPTIONS, useValue: COMMON_CONFIG }, + { provide: BUCKET, useValue: storageBucket }, + { provide: USE_EMULATOR, useValue: ['localhost', storageEmulatorPort] }, + ] + }); + + afStorage = TestBed.inject(AngularFireStorage); + }); + + describe('', () => { + + it('should exist', () => { + expect(afStorage instanceof AngularFireStorage).toBe(true); + }); + + it('should have the Firebase storage instance', () => { + expect(afStorage.storage).toBeDefined(); + }); + + it('should have an initialized Firebase app', () => { + expect(afStorage.storage.app).toBeDefined(); + }); + + it('should be hooked up the right app', () => { + expect(afStorage.storage.app.name).toEqual(firebaseAppName); + }); + + it('storage be pointing towards a different bucket', () => { + expect(afStorage.storage.ref().toString()).toEqual(`gs://${storageBucket}/`); + }); + + // TODO tests for Node? + if (typeof Blob !== 'undefined') { + + it('it should upload, download, and delete', (done) => { + const data = { angular: 'fire' }; + const blob = blobOrBuffer(JSON.stringify(data), { type: 'application/json' }); + const name = rando(); + const ref = TestBed.runInInjectionContext(() => afStorage.ref(name)); + const task = TestBed.runInInjectionContext(() => ref.put(blob)); + // Wait for the upload + forkJoin([TestBed.runInInjectionContext(() => task.snapshotChanges())]) + .pipe( + // get the url download + mergeMap(() => TestBed.runInInjectionContext(() => ref.getDownloadURL())), + // assert the URL + tap(url => expect(url).toMatch(new RegExp(`http:\\/\\/localhost:9199\\/v0\\/b\\/${storageBucket}\\/o\\/${name}`))), + // Delete the file + mergeMap(() => ref.delete()) + ) + // finish the test + .subscribe(done, done.fail); + }); + + } + + }); + +}); diff --git a/src/compat/storage/storage.ts b/src/compat/storage/storage.ts new file mode 100644 index 000000000..f820fe73b --- /dev/null +++ b/src/compat/storage/storage.ts @@ -0,0 +1,76 @@ +import { EnvironmentInjector, Inject, Injectable, InjectionToken, NgZone, Optional, PLATFORM_ID, inject } from '@angular/core'; +import { ÉĩAngularFireSchedulers } from '@angular/fire'; +import { AppCheckInstances } from '@angular/fire/app-check'; +import { FIREBASE_APP_NAME, FIREBASE_OPTIONS, ÉĩcacheInstance, ÉĩfirebaseAppFactory } from '@angular/fire/compat'; +import { FirebaseOptions } from 'firebase/app'; +import firebase from 'firebase/compat/app'; +import { UploadMetadata } from './interfaces'; +import { createStorageRef } from './ref'; +import 'firebase/compat/storage'; + +export const BUCKET = new InjectionToken('angularfire2.storageBucket'); +export const MAX_UPLOAD_RETRY_TIME = new InjectionToken('angularfire2.storage.maxUploadRetryTime'); +export const MAX_OPERATION_RETRY_TIME = new InjectionToken('angularfire2.storage.maxOperationRetryTime'); + +type UseEmulatorArguments = Parameters; +export const USE_EMULATOR = new InjectionToken('angularfire2.storage.use-emulator'); + +/** + * AngularFireStorage Service + * + * This service is the main entry point for this feature module. It provides + * an API for uploading and downloading binary files from Cloud Storage for + * Firebase. + */ +@Injectable({ + providedIn: 'any' +}) +export class AngularFireStorage { + public readonly storage: firebase.storage.Storage; + private readonly injector = inject(EnvironmentInjector); + + constructor( + @Inject(FIREBASE_OPTIONS) options: FirebaseOptions, + @Optional() @Inject(FIREBASE_APP_NAME) name: string | null | undefined, + @Optional() @Inject(BUCKET) storageBucket: string | null, + + @Inject(PLATFORM_ID) platformId: object, + zone: NgZone, + schedulers: ÉĩAngularFireSchedulers, + @Optional() @Inject(MAX_UPLOAD_RETRY_TIME) maxUploadRetryTime: any, + @Optional() @Inject(MAX_OPERATION_RETRY_TIME) maxOperationRetryTime: any, + @Optional() @Inject(USE_EMULATOR) _useEmulator: any, + @Optional() _appCheckInstances: AppCheckInstances, + ) { + const app = ÉĩfirebaseAppFactory(options, zone, name); + this.storage = ÉĩcacheInstance(`${app.name}.storage.${storageBucket}`, 'AngularFireStorage', app.name, () => { + const storage = zone.runOutsideAngular(() => app.storage(storageBucket || undefined)); + const useEmulator = _useEmulator as UseEmulatorArguments|null; + if (useEmulator) { + storage.useEmulator(...useEmulator); + } + if (maxUploadRetryTime) { + storage.setMaxUploadRetryTime(maxUploadRetryTime); + } + if (maxOperationRetryTime) { + storage.setMaxOperationRetryTime(maxOperationRetryTime); + } + return storage; + }, [maxUploadRetryTime, maxOperationRetryTime]); + } + + ref(path: string) { + return createStorageRef(this.storage.ref(path), this.injector); + } + + refFromURL(path: string) { + return createStorageRef(this.storage.refFromURL(path), this.injector); + } + + upload(path: string, data: any, metadata?: UploadMetadata) { + const storageRef = this.storage.ref(path); + const ref = createStorageRef(storageRef, this.injector); + return ref.put(data, metadata); + } + +} diff --git a/src/storage/task.ts b/src/compat/storage/task.ts similarity index 100% rename from src/storage/task.ts rename to src/compat/storage/task.ts index bccc7154c..53bba1afc 100644 --- a/src/storage/task.ts +++ b/src/compat/storage/task.ts @@ -1,7 +1,7 @@ -import { UploadTask, UploadTaskSnapshot } from './interfaces'; -import { fromTask } from './observable/fromTask'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; +import { UploadTask, UploadTaskSnapshot } from './interfaces'; +import { fromTask } from './observable/fromTask'; export interface AngularFireUploadTask { task: UploadTask; diff --git a/src/core.ts b/src/core.ts new file mode 100644 index 000000000..e0ad8f7f5 --- /dev/null +++ b/src/core.ts @@ -0,0 +1,43 @@ +import { Version } from '@angular/core'; +import { ComponentContainer } from '@firebase/component'; +import { FirebaseApp, getApps } from 'firebase/app'; + +export const VERSION = new Version('ANGULARFIRE2_VERSION'); + +export const ÉĩisSupportedError = (module: string) => + `The APP_INITIALIZER that is "making" isSupported() sync for the sake of convenient DI has not resolved in this +context. Rather than injecting ${module} in the constructor, first ensure that ${module} is supported by calling +\`await isSupported()\`, then retrieve the instance from the injector manually \`injector.get(${module})\`.`; + +// TODO is there a better way to get at the internal types? +interface FirebaseAppWithContainer extends FirebaseApp { + container: ComponentContainer; +} + +export function ÉĩgetDefaultInstanceOf(identifier: string, provided: T[]|undefined, defaultApp: FirebaseApp): T|undefined { + if (provided) { + // Was provide* only called once? If so grab that + if (provided.length === 1) { return provided[0]; } + const providedUsingDefaultApp = provided.filter((it: any) => it.app === defaultApp); + // Was provide* only called once, using the default app? If so use that + if (providedUsingDefaultApp.length === 1) { return providedUsingDefaultApp[0]; } + } + // Grab the default instance from the defaultApp + const defaultAppWithContainer: FirebaseAppWithContainer = defaultApp as any; + const provider = defaultAppWithContainer.container.getProvider(identifier as never); + return provider.getImmediate({ optional: true }); +} + +export const ÉĩgetAllInstancesOf = (identifier: string, app?: FirebaseApp): T[] => { + const apps = app ? [app] : getApps(); + const instances: any[] = []; + apps.forEach((app: FirebaseAppWithContainer) => { + const provider: any = app.container.getProvider(identifier as never); + provider.instances.forEach((instance: any) => { + if (!instances.includes(instance)) { + instances.push(instance); + } + }); + }); + return instances; +}; diff --git a/src/core/angularfire2.spec.ts b/src/core/angularfire2.spec.ts deleted file mode 100644 index d62d0b481..000000000 --- a/src/core/angularfire2.spec.ts +++ /dev/null @@ -1,246 +0,0 @@ -import { TestBed } from '@angular/core/testing'; -import { CompilerFactory, NgModule, NgZone, PlatformRef } from '@angular/core'; -import { AngularFireModule, FirebaseApp } from './public_api'; -import { Observable, of, Subject } from 'rxjs'; -import { COMMON_CONFIG } from '../test-config'; -import { BrowserModule } from '@angular/platform-browser'; -import { database } from 'firebase/app'; -import { ÉĩAngularFireSchedulers, ÉĩkeepUnstableUntilFirstFactory, ÉĩZoneScheduler } from './angularfire2'; -import { tap } from 'rxjs/operators'; -import { TestScheduler } from 'rxjs/testing'; -import { rando } from '../firestore/utils.spec'; - -describe('angularfire', () => { - let app: FirebaseApp; - let rootRef: database.Reference; - let questionsRef: database.Reference; - let listOfQuestionsRef: database.Reference; - let defaultPlatform: PlatformRef; - let appName: string; - - beforeEach(() => { - - appName = rando(); - - TestBed.configureTestingModule({ - imports: [AngularFireModule.initializeApp(COMMON_CONFIG, appName)] - }); - - app = TestBed.inject(FirebaseApp); - defaultPlatform = TestBed.inject(PlatformRef); - rootRef = app.database().ref(); - questionsRef = rootRef.child('questions'); - listOfQuestionsRef = rootRef.child('list-of-questions'); - }); - - afterEach(() => { - rootRef.remove(); - app.delete(); - }); - - describe('ZoneScheduler', () => { - it('should execute the scheduled work inside the specified zone', done => { - const ngZone = Zone.current.fork({ - name: 'ngZone' - }); - const rootZone = Zone.current; - - // Mimic real behavior: Executing in Angular - ngZone.run(() => { - const outsideAngularScheduler = new ÉĩZoneScheduler(rootZone); - outsideAngularScheduler.schedule(() => { - expect(Zone.current.name).not.toEqual('ngZone'); - done(); - }); - }); - }); - - it('should execute nested scheduled work inside the specified zone', done => { - const testScheduler = new TestScheduler(null); - testScheduler.run(helpers => { - const outsideAngularScheduler = new ÉĩZoneScheduler(Zone.current, testScheduler); - - const ngZone = Zone.current.fork({ - name: 'ngZone' - }); - - let callbacksRan = 0; - - // Mimic real behavior: Executing in Angular - ngZone.run(() => { - outsideAngularScheduler.schedule(() => { - callbacksRan++; - expect(Zone.current.name).not.toEqual('ngZone'); - - ngZone.run(() => { - // Sync queueing - outsideAngularScheduler.schedule(() => { - callbacksRan++; - expect(Zone.current.name).not.toEqual('ngZone'); - }); - - // Async (10ms delay) nested scheduling - outsideAngularScheduler.schedule(() => { - callbacksRan++; - expect(Zone.current.name).not.toEqual('ngZone'); - }, 10); - - // Simulate flush from inside angular- - helpers.flush(); - done(); - expect(callbacksRan).toEqual(3); - }); - }); - helpers.flush(); - }); - }); - }); - }); - - describe('keepUnstableUntilFirstFactory', () => { - let schedulers: ÉĩAngularFireSchedulers; - let outsideZone: Zone; - let insideZone: Zone; - beforeAll(() => { - outsideZone = Zone.current; - insideZone = Zone.current.fork({ - name: 'ngZone' - }); - const ngZone = { - run: insideZone.run.bind(insideZone), - runGuarded: insideZone.runGuarded.bind(insideZone), - runOutsideAngular: outsideZone.runGuarded.bind(outsideZone), - runTask: insideZone.run.bind(insideZone) - } as NgZone; - schedulers = new ÉĩAngularFireSchedulers(ngZone); - }); - - it('should re-schedule emissions asynchronously', done => { - const keepUnstableOp = ÉĩkeepUnstableUntilFirstFactory(schedulers); - - let ran = false; - of(null).pipe( - keepUnstableOp, - tap(() => ran = true) - ).subscribe(() => { - expect(ran).toEqual(true); - done(); - }, () => fail('Should not error')); - - expect(ran).toEqual(false); - }); - - it(`should subscribe outside angular and observe inside angular`, done => { - - const keepUnstableOp = ÉĩkeepUnstableUntilFirstFactory(schedulers); - - insideZone.run(() => { - new Observable(s => { - expect(Zone.current).toEqual(outsideZone); - s.next('test'); - }).pipe( - keepUnstableOp, - tap(() => { - expect(Zone.current).toEqual(insideZone); - }) - ).subscribe(() => { - expect(Zone.current).toEqual(insideZone); - done(); - }, err => { - fail(err); - }); - }); - - }); - - it('should block until first emission', done => { - const testScheduler = new TestScheduler(null); - testScheduler.run(helpers => { - const outsideZone = Zone.current; - // tslint:disable-next-line:no-string-literal - const taskTrack = new Zone['TaskTrackingZoneSpec'](); - const insideZone = Zone.current.fork(taskTrack); - const trackingSchedulers: ÉĩAngularFireSchedulers = { - ngZone: { - run: insideZone.run.bind(insideZone), - runGuarded: insideZone.runGuarded.bind(insideZone), - runOutsideAngular: outsideZone.runGuarded.bind(outsideZone), - runTask: insideZone.run.bind(insideZone) - } as NgZone, - outsideAngular: new ÉĩZoneScheduler(outsideZone, testScheduler), - insideAngular: new ÉĩZoneScheduler(insideZone, testScheduler) - }; - const keepUnstableOp = ÉĩkeepUnstableUntilFirstFactory(trackingSchedulers); - - const s = new Subject(); - s.pipe( - keepUnstableOp - ).subscribe(() => { - }, err => { - fail(err); - }, () => { - }); - - // Flush to ensure all async scheduled functions are run - helpers.flush(); - // Should now be blocked until first item arrives - expect(taskTrack.macroTasks.length).toBe(1); - expect(taskTrack.macroTasks[0].source).toBe('firebaseZoneBlock'); - - // Emit next item - s.next(123); - helpers.flush(); - - // TODO drop this, it's to work around my 15ms timeout hack - setTimeout(() => { - // Should not be blocked after first item - expect(taskTrack.macroTasks.length).toBe(0); - done(); - }, 150); - - }); - }); - - }); - - describe('FirebaseApp', () => { - - it('should provide a FirebaseApp for the FirebaseApp binding', () => { - expect(typeof app.delete).toBe('function'); - }); - - if (typeof window !== 'undefined') { - - it('should have the provided name', () => { - expect(app.name).toBe(appName); - }); - - it('should use an already intialized firebase app if it exists', done => { - @NgModule({ - imports: [ - AngularFireModule.initializeApp(COMMON_CONFIG, appName), - BrowserModule - ] - }) - class MyModule { - ngDoBootstrap() { - } - } - - const compilerFactory: CompilerFactory = - defaultPlatform.injector.get(CompilerFactory, null); - const moduleFactory = compilerFactory.createCompiler().compileModuleSync(MyModule); - - defaultPlatform.bootstrapModuleFactory(moduleFactory) - .then(moduleRef => { - const ref = moduleRef.injector.get(FirebaseApp); - expect(ref.name).toEqual(app.name); - }).then(done, e => { - fail(e); - done(); - }); - }); - - } - }); -}); diff --git a/src/core/angularfire2.ts b/src/core/angularfire2.ts deleted file mode 100644 index 34841d582..000000000 --- a/src/core/angularfire2.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { NgZone } from '@angular/core'; -import { - asyncScheduler, - Observable, - Operator, - queueScheduler, - SchedulerAction, - SchedulerLike, - Subscriber, - Subscription, - TeardownLogic -} from 'rxjs'; -import { observeOn, subscribeOn, tap } from 'rxjs/operators'; - -function noop() { -} - -/** - * Schedules tasks so that they are invoked inside the Zone that is passed in the constructor. - */ -// tslint:disable-next-line:class-name -export class ÉĩZoneScheduler implements SchedulerLike { - constructor(private zone: any, private delegate: any = queueScheduler) { - } - - now() { - return this.delegate.now(); - } - - schedule(work: (this: SchedulerAction, state?: any) => void, delay?: number, state?: any): Subscription { - const targetZone = this.zone; - // Wrap the specified work function to make sure that if nested scheduling takes place the - // work is executed in the correct zone - const workInZone = function(this: SchedulerAction, state: any) { - targetZone.runGuarded(() => { - work.apply(this, [state]); - }); - }; - - // Scheduling itself needs to be run in zone to ensure setInterval calls for async scheduling are done - // inside the correct zone. This scheduler needs to schedule asynchronously always to ensure that - // firebase emissions are never synchronous. Specifying a delay causes issues with the queueScheduler delegate. - return this.delegate.schedule(workInZone, delay, state); - } -} - -// tslint:disable-next-line:class-name -export class ÉĩBlockUntilFirstOperator implements Operator { - private task: MacroTask | null = null; - - constructor(private zone: any) { - } - - call(subscriber: Subscriber, source: Observable): TeardownLogic { - const unscheduleTask = this.unscheduleTask.bind(this); - this.task = this.zone.run(() => Zone.current.scheduleMacroTask('firebaseZoneBlock', noop, {}, noop, noop)); - - return source.pipe( - tap({ next: unscheduleTask, complete: unscheduleTask, error: unscheduleTask }) - ).subscribe(subscriber).add(unscheduleTask); - } - - private unscheduleTask() { - // maybe this is a race condition, invoke in a timeout - // hold for 10ms while I try to figure out what is going on - setTimeout(() => { - if (this.task != null && this.task.state === 'scheduled') { - this.task.invoke(); - this.task = null; - } - }, 10); - } -} - -// tslint:disable-next-line:class-name -export class ÉĩAngularFireSchedulers { - public readonly outsideAngular: ÉĩZoneScheduler; - public readonly insideAngular: ÉĩZoneScheduler; - - constructor(public ngZone: NgZone) { - this.outsideAngular = ngZone.runOutsideAngular(() => new ÉĩZoneScheduler(Zone.current)); - this.insideAngular = ngZone.run(() => new ÉĩZoneScheduler(Zone.current, asyncScheduler)); - } -} - -/** - * Operator to block the zone until the first value has been emitted or the observable - * has completed/errored. This is used to make sure that universal waits until the first - * value from firebase but doesn't block the zone forever since the firebase subscription - * is still alive. - */ -export function ÉĩkeepUnstableUntilFirstFactory(schedulers: ÉĩAngularFireSchedulers) { - return function keepUnstableUntilFirst(obs$: Observable): Observable { - obs$ = obs$.lift( - new ÉĩBlockUntilFirstOperator(schedulers.ngZone) - ); - - return obs$.pipe( - // Run the subscribe body outside of Angular (e.g. calling Firebase SDK to add a listener to a change event) - subscribeOn(schedulers.outsideAngular), - // Run operators inside the angular zone (e.g. side effects via tap()) - observeOn(schedulers.insideAngular) - // INVESTIGATE https://github.com/angular/angularfire/pull/2315 - // share() - ); - }; -} - -// tslint:disable:ban-types -type FunctionPropertyNames = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T]; -type PromiseReturningFunctionPropertyNames = { - [K in FunctionPropertyNames]: ReturnType extends Promise ? K : never -}[FunctionPropertyNames]; -type NonPromiseReturningFunctionPropertyNames = { - [K in FunctionPropertyNames]: ReturnType extends Promise ? never : K -}[FunctionPropertyNames]; -type NonFunctionPropertyNames = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T]; -// tslint:enable:ban-types - -export type ÉĩPromiseProxy = { [K in NonFunctionPropertyNames]: Promise } & - { [K in NonPromiseReturningFunctionPropertyNames]: (...args: Parameters) => Promise> } & - { [K in PromiseReturningFunctionPropertyNames]: (...args: Parameters) => ReturnType }; - - -// DEBUG quick debugger function for inline logging that typescript doesn't complain about -// wrote it for debugging the ÉĩlazySDKProxy, commenting out for now; should consider exposing a -// verbose mode for AngularFire in a future release that uses something like this in multiple places -// usage: () => log('something') || returnValue -// const log = (...args: any[]): false => { console.log(...args); return false } - -// The problem here are things like ngOnDestroy are missing, then triggering the service -// rather than dig too far; I'm capturing these as I go. -const noopFunctions = ['ngOnDestroy']; - -// INVESTIGATE should we make the Proxy revokable and do some cleanup? -// right now it's fairly simple but I'm sure this will grow in complexity -export const ÉĩlazySDKProxy = (klass: any, observable: Observable, zone: NgZone) => { - return new Proxy(klass, { - get: (_, name: string) => zone.runOutsideAngular(() => { - if (klass[name]) { - return klass[name]; - } - if (noopFunctions.includes(name)) { - return () => { - }; - } - const promise = observable.toPromise().then(mod => { - const ret = mod && mod[name]; - // TODO move to proper type guards - if (typeof ret === 'function') { - return ret.bind(mod); - } else if (ret && ret.then) { - return ret.then((res: any) => zone.run(() => res)); - } else { - return zone.run(() => ret); - } - }); - // recurse the proxy - return new Proxy(() => undefined, { - get: (_, name) => promise[name], - // TODO handle callbacks as transparently as I can - apply: (self, _, args) => promise.then(it => it && it(...args)) - } - ); - }) - }); -}; diff --git a/src/core/builders.json b/src/core/builders.json deleted file mode 100644 index 0a280918e..000000000 --- a/src/core/builders.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "$schema": "@angular-devkit/architect/src/builders-schema.json", - "builders": { - "deploy": { - "implementation": "./schematics/deploy/builder", - "schema": "./schematics/deploy/schema.json", - "description": "Deploy builder" - } - } - } \ No newline at end of file diff --git a/src/core/collection.json b/src/core/collection.json deleted file mode 100644 index ccbc08d77..000000000 --- a/src/core/collection.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "@angular-devkit/schematics/collection-schema.json", - "schematics": { - "ng-add": { - "description": "Add firebase deploy schematic", - "factory": "./schematics/public_api#ngAdd" - }, - "ng-add-setup-project": { - "description": "Setup ng deploy", - "factory": "./schematics/public_api#ngAddSetupProject" - } - } - } diff --git a/src/core/firebase.app.module.ts b/src/core/firebase.app.module.ts deleted file mode 100644 index 2e499b208..000000000 --- a/src/core/firebase.app.module.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Inject, InjectionToken, NgModule, NgZone, Optional, PLATFORM_ID, VERSION as NG_VERSION, Version } from '@angular/core'; -import firebase from 'firebase/app'; -import { analytics, app, auth, database, firestore, functions, messaging, performance, remoteConfig, storage } from 'firebase/app'; - -// INVESTIGATE Public types don't expose FirebaseOptions or FirebaseAppConfig, is this the case anylonger? -export interface FirebaseOptions { - [key: string]: any; -} - -export interface FirebaseAppConfig { - [key: string]: any; -} - -export const FIREBASE_OPTIONS = new InjectionToken('angularfire2.app.options'); -export const FIREBASE_APP_NAME = new InjectionToken('angularfire2.app.nameOrConfig'); - -// Have to implement as we need to return a class from the provider, we should consider exporting -// this in the firebase/app types as this is our highest risk of breaks -export class FirebaseApp implements Partial { - name: string; - options: {}; - analytics: () => analytics.Analytics; - auth: () => auth.Auth; - database: (databaseURL?: string) => database.Database; - messaging: () => messaging.Messaging; - performance: () => performance.Performance; - storage: (storageBucket?: string) => storage.Storage; - delete: () => Promise; - firestore: () => firestore.Firestore; - functions: (region?: string) => functions.Functions; - remoteConfig: () => remoteConfig.RemoteConfig; -} - -export const VERSION = new Version('ANGULARFIRE2_VERSION'); - -export function ÉĩfirebaseAppFactory(options: FirebaseOptions, zone: NgZone, nameOrConfig?: string | FirebaseAppConfig | null) { - const name = typeof nameOrConfig === 'string' && nameOrConfig || '[DEFAULT]'; - const config = typeof nameOrConfig === 'object' && nameOrConfig || {}; - config.name = config.name || name; - // Added any due to some inconsistency between @firebase/app and firebase types - const existingApp = firebase.apps.filter(app => app && app.name === config.name)[0] as any; - // We support FirebaseConfig, initializeApp's public type only accepts string; need to cast as any - // Could be solved with https://github.com/firebase/firebase-js-sdk/pull/1206 - return (existingApp || zone.runOutsideAngular(() => firebase.initializeApp(options, config as any))) as FirebaseApp; -} - -const FIREBASE_APP_PROVIDER = { - provide: FirebaseApp, - useFactory: ÉĩfirebaseAppFactory, - deps: [ - FIREBASE_OPTIONS, - NgZone, - [new Optional(), FIREBASE_APP_NAME] - ] -}; - -@NgModule({ - providers: [FIREBASE_APP_PROVIDER] -}) -export class AngularFireModule { - static initializeApp(options: FirebaseOptions, nameOrConfig?: string | FirebaseAppConfig) { - return { - ngModule: AngularFireModule, - providers: [ - { provide: FIREBASE_OPTIONS, useValue: options }, - { provide: FIREBASE_APP_NAME, useValue: nameOrConfig } - ] - }; - } - - // tslint:disable-next-line:ban-types - constructor(@Inject(PLATFORM_ID) platformId: Object) { - firebase.registerVersion('angularfire', VERSION.full, platformId.toString()); - firebase.registerVersion('angular', NG_VERSION.full); - } -} diff --git a/src/core/public_api.ts b/src/core/public_api.ts deleted file mode 100644 index 77f50e30e..000000000 --- a/src/core/public_api.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './angularfire2'; -export * from './firebase.app.module'; diff --git a/src/data-connect/data-connect.module.ts b/src/data-connect/data-connect.module.ts new file mode 100644 index 000000000..980a22e7c --- /dev/null +++ b/src/data-connect/data-connect.module.ts @@ -0,0 +1,74 @@ +import { + EnvironmentProviders, + InjectionToken, + Injector, + NgModule, + NgZone, + Optional, + makeEnvironmentProviders, +} from '@angular/core'; +import { VERSION, ÉĩAngularFireSchedulers, ÉĩgetDefaultInstanceOf } from '@angular/fire'; +import { FirebaseApp, FirebaseApps } from '@angular/fire/app'; +import { registerVersion } from 'firebase/app'; +import { DATA_CONNECT_PROVIDER_NAME, DataConnect, DataConnectInstances } from './data-connect'; + +const PROVIDED_DATA_CONNECT_INSTANCES = new InjectionToken('angularfire2.data-connect-instances'); + +export function defaultDataConnectInstanceFactory(provided: DataConnect[]|undefined, defaultApp: FirebaseApp) { + return ÉĩgetDefaultInstanceOf(DATA_CONNECT_PROVIDER_NAME, provided, defaultApp); +} + +export function dataConnectInstanceFactory(fn: (injector: Injector) => DataConnect) { + return (zone: NgZone, injector: Injector) => { + return zone.runOutsideAngular(() => fn(injector)); + }; +} + +const DATA_CONNECT_INSTANCES_PROVIDER = { + provide: DataConnectInstances, + deps: [ + [new Optional(), PROVIDED_DATA_CONNECT_INSTANCES ], + ] +}; + +const DEFAULT_DATA_CONNECT_INSTANCE_PROVIDER = { + provide: DataConnect, + useFactory: defaultDataConnectInstanceFactory, + deps: [ + [new Optional(), PROVIDED_DATA_CONNECT_INSTANCES ], + FirebaseApp, + ] +}; + +@NgModule({ + providers: [ + DEFAULT_DATA_CONNECT_INSTANCE_PROVIDER, + DATA_CONNECT_INSTANCES_PROVIDER, + ] +}) +export class DataConnectModule { + constructor() { + registerVersion('angularfire', VERSION.full, 'fdc'); + } +} + +export function provideDataConnect(fn: (injector: Injector) => DataConnect, ...deps: any[]): EnvironmentProviders { + registerVersion('angularfire', VERSION.full, 'fdc'); + + return makeEnvironmentProviders([ + DEFAULT_DATA_CONNECT_INSTANCE_PROVIDER, + DATA_CONNECT_INSTANCES_PROVIDER, + { + provide: PROVIDED_DATA_CONNECT_INSTANCES, + useFactory: dataConnectInstanceFactory(fn), + multi: true, + deps: [ + NgZone, + Injector, + ÉĩAngularFireSchedulers, + FirebaseApps, + ...deps, + ], + } + ]); +} diff --git a/src/data-connect/data-connect.spec.ts b/src/data-connect/data-connect.spec.ts new file mode 100644 index 000000000..91c7458de --- /dev/null +++ b/src/data-connect/data-connect.spec.ts @@ -0,0 +1,38 @@ +/* +import { TestBed } from '@angular/core/testing'; +import { getApp, initializeApp, provideFirebaseApp } from '@angular/fire/app'; +import { DataConnect, getDataConnect, provideDataConnect } from '@angular/fire/data-connect'; +import { COMMON_CONFIG } from '../test-config'; +import { rando } from '../utils'; + + +describe('DataConnect', () => { + let dataConnect: DataConnect; + let providedDataConnect: DataConnect; + let appName: string; + + describe('single injection', () => { + + beforeEach(() => { + appName = rando(); + TestBed.configureTestingModule({ + providers: [ + provideFirebaseApp(() => initializeApp(COMMON_CONFIG, appName)), + provideDataConnect(() => { + providedDataConnect = getDataConnect(getApp(appName)); + return providedDataConnect; + }), + ], + }); + dataConnect = TestBed.inject(DataConnect); + }); + + it('should be injectable', () => { + expect(providedDataConnect).toBeTruthy(); + expect(dataConnect).toEqual(providedDataConnect); + }); + + }); + +}); +*/ \ No newline at end of file diff --git a/src/data-connect/data-connect.ts b/src/data-connect/data-connect.ts new file mode 100644 index 000000000..0fc4fac8e --- /dev/null +++ b/src/data-connect/data-connect.ts @@ -0,0 +1,22 @@ +import { ÉĩgetAllInstancesOf } from '@angular/fire'; +import { DataConnect } from 'firebase/data-connect'; +import { from, timer } from 'rxjs'; +import { concatMap, distinct } from 'rxjs/operators'; + +export { DataConnect }; + +export const DATA_CONNECT_PROVIDER_NAME = 'data-connect'; + + +export interface DataConnectInstances extends Array {} + +export class DataConnectInstances { + constructor() { + return ÉĩgetAllInstancesOf(DATA_CONNECT_PROVIDER_NAME); + } +} + +export const dataConnectInstance$ = timer(0, 300).pipe( + concatMap(() => from(ÉĩgetAllInstancesOf(DATA_CONNECT_PROVIDER_NAME))), + distinct(), +); diff --git a/src/data-connect/firebase.ts b/src/data-connect/firebase.ts new file mode 100644 index 000000000..2597117d8 --- /dev/null +++ b/src/data-connect/firebase.ts @@ -0,0 +1,26 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +export * from 'firebase/data-connect'; +import { ÉĩzoneWrap } from '@angular/fire'; +import { + connectDataConnectEmulator as _connectDataConnectEmulator, + executeMutation as _executeMutation, + executeQuery as _executeQuery, + getDataConnect as _getDataConnect, + mutationRef as _mutationRef, + queryRef as _queryRef, + setLogLevel as _setLogLevel, + subscribe as _subscribe, + terminate as _terminate, + toQueryRef as _toQueryRef +} from 'firebase/data-connect'; + +export const connectDataConnectEmulator = ÉĩzoneWrap(_connectDataConnectEmulator, true); +export const executeMutation = ÉĩzoneWrap(_executeMutation, true); +export const executeQuery = ÉĩzoneWrap(_executeQuery, true); +export const getDataConnect = ÉĩzoneWrap(_getDataConnect, true); +export const mutationRef = ÉĩzoneWrap(_mutationRef, true, 2); +export const queryRef = ÉĩzoneWrap(_queryRef, true, 2); +export const setLogLevel = ÉĩzoneWrap(_setLogLevel, true); +export const subscribe = ÉĩzoneWrap(_subscribe, true); +export const terminate = ÉĩzoneWrap(_terminate, true); +export const toQueryRef = ÉĩzoneWrap(_toQueryRef, true, 2); diff --git a/src/data-connect/ng-package.json b/src/data-connect/ng-package.json new file mode 100644 index 000000000..d3032da35 --- /dev/null +++ b/src/data-connect/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/data-connect/overrides.ts b/src/data-connect/overrides.ts new file mode 100644 index 000000000..79a81fc23 --- /dev/null +++ b/src/data-connect/overrides.ts @@ -0,0 +1,3 @@ +import { isMessagingSupportedFactory } from './is-messaging-supported-factory'; + +export const isSupported = isMessagingSupportedFactory.async; diff --git a/src/data-connect/package.json b/src/data-connect/package.json new file mode 100644 index 000000000..8dce418a5 --- /dev/null +++ b/src/data-connect/package.json @@ -0,0 +1,3 @@ +{ + "$schema": "../../node_modules/ng-packagr/package.schema.json" +} diff --git a/src/data-connect/public_api.ts b/src/data-connect/public_api.ts new file mode 100644 index 000000000..6550c0b10 --- /dev/null +++ b/src/data-connect/public_api.ts @@ -0,0 +1,3 @@ +export { DataConnectInstances, DataConnect, dataConnectInstance$ } from './data-connect'; +export { provideDataConnect, DataConnectModule } from './data-connect.module'; +export * from './firebase'; diff --git a/src/database/database.module.ts b/src/database/database.module.ts index ea183f8ec..7ded9070f 100644 --- a/src/database/database.module.ts +++ b/src/database/database.module.ts @@ -1,7 +1,81 @@ -import { NgModule } from '@angular/core'; -import { AngularFireDatabase } from './database'; +import { + EnvironmentProviders, + InjectionToken, + Injector, + NgModule, + NgZone, + Optional, + makeEnvironmentProviders, +} from '@angular/core'; +import { VERSION, ÉĩAngularFireSchedulers, ÉĩgetDefaultInstanceOf } from '@angular/fire'; +import { FirebaseApp, FirebaseApps } from '@angular/fire/app'; +import { AppCheckInstances } from '@angular/fire/app-check'; +import { AuthInstances } from '@angular/fire/auth'; +import { registerVersion } from 'firebase/app'; +import { Database as FirebaseDatabase } from 'firebase/database'; +import { DATABASE_PROVIDER_NAME, Database, DatabaseInstances } from './database'; + +export const PROVIDED_DATABASE_INSTANCES = new InjectionToken('angularfire2.database-instances'); + +export function defaultDatabaseInstanceFactory(provided: FirebaseDatabase[]|undefined, defaultApp: FirebaseApp) { + const defaultDatabase = ÉĩgetDefaultInstanceOf(DATABASE_PROVIDER_NAME, provided, defaultApp); + return defaultDatabase && new Database(defaultDatabase); +} + +export function databaseInstanceFactory(fn: (injector: Injector) => FirebaseDatabase) { + return (zone: NgZone, injector: Injector) => { + const database = zone.runOutsideAngular(() => fn(injector)); + return new Database(database); + }; +} + +const DATABASE_INSTANCES_PROVIDER = { + provide: DatabaseInstances, + deps: [ + [new Optional(), PROVIDED_DATABASE_INSTANCES ], + ] +}; + +const DEFAULT_DATABASE_INSTANCE_PROVIDER = { + provide: Database, + useFactory: defaultDatabaseInstanceFactory, + deps: [ + [new Optional(), PROVIDED_DATABASE_INSTANCES ], + FirebaseApp, + ] +}; @NgModule({ - providers: [ AngularFireDatabase ] + providers: [ + DEFAULT_DATABASE_INSTANCE_PROVIDER, + DATABASE_INSTANCES_PROVIDER, + ] }) -export class AngularFireDatabaseModule { } +export class DatabaseModule { + constructor() { + registerVersion('angularfire', VERSION.full, 'rtdb'); + } +} + +export function provideDatabase(fn: (injector: Injector) => FirebaseDatabase, ...deps: any[]): EnvironmentProviders { + registerVersion('angularfire', VERSION.full, 'rtdb'); + return makeEnvironmentProviders([ + DEFAULT_DATABASE_INSTANCE_PROVIDER, + DATABASE_INSTANCES_PROVIDER, + { + provide: PROVIDED_DATABASE_INSTANCES, + useFactory: databaseInstanceFactory(fn), + multi: true, + deps: [ + NgZone, + Injector, + ÉĩAngularFireSchedulers, + FirebaseApps, + // Database+Auth work better if Auth is loaded first + [new Optional(), AuthInstances ], + [new Optional(), AppCheckInstances ], + ...deps, + ] + } + ]); +} diff --git a/src/database/database.spec.ts b/src/database/database.spec.ts index fce81c16c..003464a4f 100644 --- a/src/database/database.spec.ts +++ b/src/database/database.spec.ts @@ -1,121 +1,39 @@ -import { AngularFireModule, FIREBASE_APP_NAME, FIREBASE_OPTIONS, FirebaseApp } from '@angular/fire'; -import { AngularFireDatabase, AngularFireDatabaseModule, URL } from './public_api'; import { TestBed } from '@angular/core/testing'; +import { FirebaseApp, getApp, initializeApp, provideFirebaseApp } from '@angular/fire/app'; +import { Database, connectDatabaseEmulator, getDatabase, provideDatabase } from '@angular/fire/database'; import { COMMON_CONFIG } from '../test-config'; -import { NgZone } from '@angular/core'; -import 'firebase/database'; -import { rando } from '../firestore/utils.spec'; +import { rando } from '../utils'; -describe('AngularFireDatabase', () => { +describe('Database', () => { let app: FirebaseApp; - let db: AngularFireDatabase; - let zone: NgZone; - let firebaseAppName: string; - - beforeEach(() => { - firebaseAppName = rando(); - TestBed.configureTestingModule({ - imports: [ - AngularFireModule.initializeApp(COMMON_CONFIG, firebaseAppName), - AngularFireDatabaseModule - ], - providers: [ - { provide: URL, useValue: 'http://localhost:9000' } - ] - }); - - app = TestBed.inject(FirebaseApp); - db = TestBed.inject(AngularFireDatabase); - zone = TestBed.inject(NgZone); - }); - - afterEach(() => { - app.delete(); - }); - - describe('', () => { - - it('should be an AngularFireDatabase type', () => { - expect(db instanceof AngularFireDatabase).toEqual(true); - }); - - it('should have an initialized Firebase app', () => { - expect(db.database.app).toBeDefined(); - }); - - it('should accept a Firebase App in the constructor', (done) => { - const database = new AngularFireDatabase(app.options, rando(), undefined, {}, zone); - expect(database instanceof AngularFireDatabase).toEqual(true); - database.database.app.delete().then(done, done); - }); - - it('should have an initialized Firebase app instance member', () => { - expect(db.database.app.name).toEqual(firebaseAppName); - }); - - }); - -}); - -describe('AngularFireDatabase w/options', () => { - let app: FirebaseApp; - let db: AngularFireDatabase; - let firebaseAppName: string; - let url: string; - let query: string; - - beforeEach(() => { - query = rando(); - firebaseAppName = rando(); - url = `http://localhost:${Math.floor(Math.random() * 9999)}`; - TestBed.configureTestingModule({ - imports: [ - AngularFireModule.initializeApp(COMMON_CONFIG, rando()), - AngularFireDatabaseModule - ], - providers: [ - { provide: FIREBASE_APP_NAME, useValue: firebaseAppName }, - { provide: FIREBASE_OPTIONS, useValue: COMMON_CONFIG }, - { provide: URL, useValue: url } - ] - }); - - app = TestBed.inject(FirebaseApp); - db = TestBed.inject(AngularFireDatabase); - }); - - afterEach(() => { - app.delete(); - }); - - describe('', () => { - - it('should be an AngularFireDatabase type', () => { - expect(db instanceof AngularFireDatabase).toEqual(true); - }); - - it('should have an initialized Firebase app', () => { - expect(db.database.app).toBeDefined(); + let database: Database; + let providedDatabase: Database; + let appName: string; + + describe('single injection', () => { + + beforeEach(() => { + appName = rando(); + TestBed.configureTestingModule({ + providers: [ + provideFirebaseApp(() => initializeApp(COMMON_CONFIG, appName)), + provideDatabase(() => { + providedDatabase = getDatabase(getApp(appName)); + connectDatabaseEmulator(providedDatabase, 'localhost', 9002); + return providedDatabase; + }), + ], + }); + app = TestBed.inject(FirebaseApp); + database = TestBed.inject(Database); }); - it('should have an initialized Firebase app instance member', () => { - expect(db.database.app.name).toEqual(firebaseAppName); + it('should be injectable', () => { + expect(providedDatabase).toBeTruthy(); + expect(database).toEqual(providedDatabase); + expect(database.app).toEqual(app); }); - /* INVESTIGATE database(url) does not seem to be working - - it('database be pointing to the provided DB instance', () => { - expect(db.database.ref().toString()).toEqual(url); - }); - - it('list should be using the provided DB instance', () => { - expect(db.list(query).query.toString()).toEqual(`${url}/${query}`); - }); - - it('object should be using the provided DB instance', () => { - expect(db.object(query).query.toString()).toEqual(`${url}/${query}`); - }); - */ }); }); diff --git a/src/database/database.ts b/src/database/database.ts index e58c92d24..169ca1bb1 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -1,83 +1,30 @@ -import { Inject, Injectable, InjectionToken, NgZone, Optional, PLATFORM_ID } from '@angular/core'; -import { AngularFireList, AngularFireObject, DatabaseQuery, PathReference, QueryFn } from './interfaces'; -import { getRef } from './utils'; -import { createListReference } from './list/create-reference'; -import { createObjectReference } from './object/create-reference'; -import { - FIREBASE_APP_NAME, - FIREBASE_OPTIONS, - FirebaseAppConfig, - FirebaseOptions, - ÉĩAngularFireSchedulers, - ÉĩfirebaseAppFactory, - ÉĩkeepUnstableUntilFirstFactory -} from '@angular/fire'; -import { Observable } from 'rxjs'; -import { database } from 'firebase/app'; -import 'firebase/database'; -import { registerDatabase } from '@firebase/database'; -import firebase from 'firebase/app'; - -export const URL = new InjectionToken('angularfire2.realtimeDatabaseURL'); - -@Injectable({ - providedIn: 'any' -}) -export class AngularFireDatabase { - public readonly database: database.Database; - - public readonly schedulers: ÉĩAngularFireSchedulers; - public readonly keepUnstableUntilFirst: (obs$: Observable) => Observable; - - constructor( - @Inject(FIREBASE_OPTIONS) options: FirebaseOptions, - @Optional() @Inject(FIREBASE_APP_NAME) nameOrConfig: string | FirebaseAppConfig | null | undefined, - @Optional() @Inject(URL) databaseURL: string | null, - // tslint:disable-next-line:ban-types - @Inject(PLATFORM_ID) platformId: Object, - zone: NgZone - ) { - this.schedulers = new ÉĩAngularFireSchedulers(zone); - this.keepUnstableUntilFirst = ÉĩkeepUnstableUntilFirstFactory(this.schedulers); - - this.database = zone.runOutsideAngular(() => { - const app = ÉĩfirebaseAppFactory(options, zone, nameOrConfig); - if (registerDatabase) { - registerDatabase(firebase as any); - } - return app.database(databaseURL || undefined); - }); +import { ÉĩgetAllInstancesOf } from '@angular/fire'; +import { Database as FirebaseDatabase } from 'firebase/database'; +import { from, timer } from 'rxjs'; +import { concatMap, distinct } from 'rxjs/operators'; + +// see notes in core/firebase.app.module.ts for why we're building the class like this + +export interface Database extends FirebaseDatabase {} + +export class Database { + constructor(database: FirebaseDatabase) { + return database; } +} - list(pathOrRef: PathReference, queryFn?: QueryFn): AngularFireList { - const ref = getRef(this.database, pathOrRef); - let query: DatabaseQuery = ref; - if (queryFn) { - query = queryFn(ref); - } - return createListReference(query, this); - } +export const DATABASE_PROVIDER_NAME = 'database'; - object(pathOrRef: PathReference): AngularFireObject { - const ref = getRef(this.database, pathOrRef); - return createObjectReference(ref, this); - } + +export interface DatabaseInstances extends Array {} - createPushId() { - return this.database.ref().push().key; +export class DatabaseInstances { + constructor() { + return ÉĩgetAllInstancesOf(DATABASE_PROVIDER_NAME); } - } -export { - PathReference, - DatabaseSnapshot, - ChildEvent, - ListenEvent, - QueryFn, - AngularFireList, - AngularFireObject, - AngularFireAction, - Action, - SnapshotAction -} from './interfaces'; +export const databaseInstance$ = timer(0, 300).pipe( + concatMap(() => from(ÉĩgetAllInstancesOf(DATABASE_PROVIDER_NAME))), + distinct(), +); diff --git a/src/database/firebase.ts b/src/database/firebase.ts new file mode 100644 index 000000000..738794e8e --- /dev/null +++ b/src/database/firebase.ts @@ -0,0 +1,82 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +export * from 'firebase/database'; +import { ÉĩzoneWrap } from '@angular/fire'; +import { + child as _child, + connectDatabaseEmulator as _connectDatabaseEmulator, + enableLogging as _enableLogging, + endAt as _endAt, + endBefore as _endBefore, + equalTo as _equalTo, + forceLongPolling as _forceLongPolling, + forceWebSockets as _forceWebSockets, + get as _get, + getDatabase as _getDatabase, + goOffline as _goOffline, + goOnline as _goOnline, + increment as _increment, + limitToFirst as _limitToFirst, + limitToLast as _limitToLast, + off as _off, + onChildAdded as _onChildAdded, + onChildChanged as _onChildChanged, + onChildMoved as _onChildMoved, + onChildRemoved as _onChildRemoved, + onDisconnect as _onDisconnect, + onValue as _onValue, + orderByChild as _orderByChild, + orderByKey as _orderByKey, + orderByPriority as _orderByPriority, + orderByValue as _orderByValue, + push as _push, + query as _query, + ref as _ref, + refFromURL as _refFromURL, + remove as _remove, + runTransaction as _runTransaction, + set as _set, + setPriority as _setPriority, + setWithPriority as _setWithPriority, + startAfter as _startAfter, + startAt as _startAt, + update as _update +} from 'firebase/database'; + +export const child = ÉĩzoneWrap(_child, true, 2); +export const connectDatabaseEmulator = ÉĩzoneWrap(_connectDatabaseEmulator, true); +export const enableLogging = ÉĩzoneWrap(_enableLogging, true); +export const endAt = ÉĩzoneWrap(_endAt, true, 2); +export const endBefore = ÉĩzoneWrap(_endBefore, true, 2); +export const equalTo = ÉĩzoneWrap(_equalTo, true, 2); +export const forceLongPolling = ÉĩzoneWrap(_forceLongPolling, true); +export const forceWebSockets = ÉĩzoneWrap(_forceWebSockets, true); +export const get = ÉĩzoneWrap(_get, true); +export const getDatabase = ÉĩzoneWrap(_getDatabase, true); +export const goOffline = ÉĩzoneWrap(_goOffline, true); +export const goOnline = ÉĩzoneWrap(_goOnline, true); +export const increment = ÉĩzoneWrap(_increment, true, 2); +export const limitToFirst = ÉĩzoneWrap(_limitToFirst, true, 2); +export const limitToLast = ÉĩzoneWrap(_limitToLast, true, 2); +export const off = ÉĩzoneWrap(_off, true); +export const onChildAdded = ÉĩzoneWrap(_onChildAdded, true); +export const onChildChanged = ÉĩzoneWrap(_onChildChanged, true); +export const onChildMoved = ÉĩzoneWrap(_onChildMoved, true); +export const onChildRemoved = ÉĩzoneWrap(_onChildRemoved, true); +export const onDisconnect = ÉĩzoneWrap(_onDisconnect, true); +export const onValue = ÉĩzoneWrap(_onValue, true); +export const orderByChild = ÉĩzoneWrap(_orderByChild, true, 2); +export const orderByKey = ÉĩzoneWrap(_orderByKey, true, 2); +export const orderByPriority = ÉĩzoneWrap(_orderByPriority, true, 2); +export const orderByValue = ÉĩzoneWrap(_orderByValue, true, 2); +export const push = ÉĩzoneWrap(_push, true, 2); +export const query = ÉĩzoneWrap(_query, true, 2); +export const ref = ÉĩzoneWrap(_ref, true, 2); +export const refFromURL = ÉĩzoneWrap(_refFromURL, true, 2); +export const remove = ÉĩzoneWrap(_remove, true, 2); +export const runTransaction = ÉĩzoneWrap(_runTransaction, true); +export const set = ÉĩzoneWrap(_set, true, 2); +export const setPriority = ÉĩzoneWrap(_setPriority, true, 2); +export const setWithPriority = ÉĩzoneWrap(_setWithPriority, true, 2); +export const startAfter = ÉĩzoneWrap(_startAfter, true, 2); +export const startAt = ÉĩzoneWrap(_startAt, true, 2); +export const update = ÉĩzoneWrap(_update, true, 2); diff --git a/src/database/ng-package.json b/src/database/ng-package.json new file mode 100644 index 000000000..d3032da35 --- /dev/null +++ b/src/database/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/database/package.json b/src/database/package.json index 10e70bb74..8dce418a5 100644 --- a/src/database/package.json +++ b/src/database/package.json @@ -1,12 +1,3 @@ { - "$schema": "../../node_modules/ng-packagr/package.schema.json", - "ngPackage": { - "lib": { - "umdModuleIds": { - "firebase/app": "firebase", - "@firebase/database": "firebase-database" - }, - "entryFile": "public_api.ts" - } - } + "$schema": "../../node_modules/ng-packagr/package.schema.json" } diff --git a/src/database/public_api.ts b/src/database/public_api.ts index b1f0ab756..61543a242 100644 --- a/src/database/public_api.ts +++ b/src/database/public_api.ts @@ -1,8 +1,4 @@ -export * from './database'; -export * from './list/changes'; -export * from './list/create-reference'; -export * from './list/snapshot-changes'; -export * from './list/state-changes'; -export * from './list/audit-trail'; -export * from './observable/fromRef'; -export * from './database.module'; +export { Database, DatabaseInstances, databaseInstance$ } from './database'; +export { provideDatabase, DatabaseModule } from './database.module'; +export * from './rxfire'; +export * from './firebase'; diff --git a/src/database/rxfire.ts b/src/database/rxfire.ts new file mode 100644 index 000000000..b34588af7 --- /dev/null +++ b/src/database/rxfire.ts @@ -0,0 +1,26 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +import { ÉĩzoneWrap } from '@angular/fire'; +import { + auditTrail as _auditTrail, + changeToData as _changeToData, + fromRef as _fromRef, + list as _list, + listVal as _listVal, + object as _object, + objectVal as _objectVal, + stateChanges as _stateChanges +} from 'rxfire/database'; + +export { + ListenEvent, + ListenerMethods +} from 'rxfire/database'; + +export const auditTrail = ÉĩzoneWrap(_auditTrail, true); +export const changeToData = ÉĩzoneWrap(_changeToData, true); +export const fromRef = ÉĩzoneWrap(_fromRef, true); +export const list = ÉĩzoneWrap(_list, true); +export const listVal = ÉĩzoneWrap(_listVal, true); +export const object = ÉĩzoneWrap(_object, true); +export const objectVal = ÉĩzoneWrap(_objectVal, true); +export const stateChanges = ÉĩzoneWrap(_stateChanges, true); diff --git a/src/firestore/collection-group/collection-group.spec.ts b/src/firestore/collection-group/collection-group.spec.ts deleted file mode 100644 index 4c199fb17..000000000 --- a/src/firestore/collection-group/collection-group.spec.ts +++ /dev/null @@ -1,489 +0,0 @@ -import { AngularFireModule, FirebaseApp } from '@angular/fire'; -import { AngularFirestore, AngularFirestoreCollectionGroup, AngularFirestoreModule, SETTINGS } from '../public_api'; -import { Query, QueryGroupFn } from '../interfaces'; -import { BehaviorSubject } from 'rxjs'; -import { skip, switchMap, take } from 'rxjs/operators'; -import { TestBed } from '@angular/core/testing'; -import { COMMON_CONFIG } from '../../test-config'; -import 'firebase/firestore'; - -import { - createRandomStocks, - delayAdd, - delayDelete, - delayUpdate, - deleteThemAll, - FAKE_STOCK_DATA, - rando, - randomName, - Stock -} from '../utils.spec'; - -async function collectionHarness(afs: AngularFirestore, items: number, queryGroupFn?: QueryGroupFn) { - const randomCollectionName = randomName(afs.firestore); - const ref = afs.firestore.collection(`${randomCollectionName}`); - const firestore: any = afs.firestore; - const collectionGroup: Query = firestore.collectionGroup(randomCollectionName); - const queryFn = queryGroupFn || (ref => ref); - const stocks = new AngularFirestoreCollectionGroup(queryFn(collectionGroup), afs); - const names = await createRandomStocks(afs.firestore, ref, items); - return { randomCollectionName, ref, stocks, names }; -} - -describe('AngularFirestoreCollectionGroup', () => { - let app: FirebaseApp; - let afs: AngularFirestore; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - AngularFireModule.initializeApp(COMMON_CONFIG, rando()), - AngularFirestoreModule - ], - providers: [ - { provide: SETTINGS, useValue: { host: 'localhost:8080', ssl: false } } - ] - }); - - app = TestBed.inject(FirebaseApp); - afs = TestBed.inject(AngularFirestore); - }); - - afterEach(() => { - app.delete(); - }); - - describe('valueChanges()', () => { - - it('should get unwrapped snapshot', async (done: any) => { - const ITEMS = 4; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - - const sub = stocks.valueChanges().subscribe(data => { - // unsub immediately as we will be deleting data at the bottom - // and that will trigger another subscribe callback and fail - // the test - sub.unsubscribe(); - // We added four things. This should be four. - // This could not be four if the batch failed or - // if the collection state is altered during a test run - expect(data.length).toEqual(ITEMS); - data.forEach(stock => { - // We used the same piece of data so they should all equal - expect(stock).toEqual(FAKE_STOCK_DATA); - }); - // Delete them all - const promises = names.map(name => ref.doc(name).delete()); - Promise.all(promises).then(done).catch(fail); - }); - - }); - - it('should handle multiple subscriptions (hot)', async (done: any) => { - const ITEMS = 4; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - const changes = stocks.valueChanges(); - const sub = changes.subscribe(() => { - }).add( - changes.pipe(take(1)).subscribe(data => { - expect(data.length).toEqual(ITEMS); - sub.unsubscribe(); - }) - ).add(() => { - deleteThemAll(names, ref).then(done).catch(done.fail); - }); - }); - - it('should handle multiple subscriptions (warm)', async (done: any) => { - const ITEMS = 4; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - const changes = stocks.valueChanges(); - changes.pipe(take(1)).subscribe(() => { - }).add(() => { - changes.pipe(take(1)).subscribe(data => { - expect(data.length).toEqual(ITEMS); - }).add(() => { - deleteThemAll(names, ref).then(done).catch(done.fail); - }); - }); - }); - - it('should handle dynamic queries that return empty sets', async (done) => { - const ITEMS = 10; - let count = 0; - - const pricefilter$ = new BehaviorSubject(null); - const randomCollectionName = randomName(afs.firestore); - const ref = afs.firestore.collection(`${randomCollectionName}`); - const names = await createRandomStocks(afs.firestore, ref, ITEMS); - const sub = pricefilter$.pipe(switchMap(price => { - return afs.collection(randomCollectionName, ref => price ? ref.where('price', '==', price) : ref).valueChanges(); - })).subscribe(data => { - count = count + 1; - // the first time should all be 'added' - if (count === 1) { - expect(data.length).toEqual(ITEMS); - pricefilter$.next(-1); - } - // on the second round, we should have filtered out everything - if (count === 2) { - expect(data.length).toEqual(0); - sub.unsubscribe(); - deleteThemAll(names, ref).then(done).catch(done.fail); - } - }); - }); - - }); - - describe('snapshotChanges()', () => { - - it('should listen to all snapshotChanges() by default', async (done) => { - const ITEMS = 10; - let count = 0; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - const sub = stocks.snapshotChanges().subscribe(data => { - count = count + 1; - // the first time should all be 'added' - if (count === 1) { - // make an update - ref.doc(names[0]).update({ price: 2 }); - } - // on the second round, make sure the array is still the same - // length but the updated item is now modified - if (count === 2) { - expect(data.length).toEqual(ITEMS); - const change = data.filter(x => x.payload.doc.id === names[0])[0]; - expect(change.type).toEqual('modified'); - sub.unsubscribe(); - deleteThemAll(names, ref).then(done).catch(done.fail); - } - }); - }); - - it('should handle multiple subscriptions (hot)', async (done: any) => { - const ITEMS = 4; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - const changes = stocks.snapshotChanges(); - const sub = changes.subscribe(() => { - }).add( - changes.pipe(take(1)).subscribe(data => { - expect(data.length).toEqual(ITEMS); - sub.unsubscribe(); - }) - ).add(() => { - deleteThemAll(names, ref).then(done).catch(done.fail); - }); - }); - - it('should handle multiple subscriptions (warm)', async (done: any) => { - const ITEMS = 4; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - const changes = stocks.snapshotChanges(); - changes.pipe(take(1)).subscribe(() => { - }).add(() => { - changes.pipe(take(1)).subscribe(data => { - expect(data.length).toEqual(ITEMS); - }).add(() => { - deleteThemAll(names, ref).then(done).catch(done.fail); - }); - }); - }); - - it('should update order on queries', async (done) => { - const ITEMS = 10; - let count = 0; - let firstIndex = 0; - const { ref, stocks, names } = - await collectionHarness(afs, ITEMS, ref => ref.orderBy('price', 'desc')); - const sub = stocks.snapshotChanges().subscribe(data => { - count = count + 1; - // the first time should all be 'added' - if (count === 1) { - // make an update - firstIndex = data.filter(d => d.payload.doc.id === names[0])[0].payload.newIndex; - ref.doc(names[0]).update({ price: 2 }); - } - // on the second round, make sure the array is still the same - // length but the updated item is now modified - if (count === 2) { - expect(data.length).toEqual(ITEMS); - const change = data.filter(x => x.payload.doc.id === names[0])[0]; - expect(change.type).toEqual('modified'); - expect(change.payload.oldIndex).toEqual(firstIndex); - sub.unsubscribe(); - deleteThemAll(names, ref).then(done).catch(done.fail); - } - }); - }); - - it('should be able to filter snapshotChanges() types - modified', async (done) => { - const ITEMS = 10; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - - const sub = stocks.snapshotChanges(['modified']).pipe(skip(1)).subscribe(data => { - sub.unsubscribe(); - const change = data.filter(x => x.payload.doc.id === names[0])[0]; - expect(data.length).toEqual(1); - expect(change.payload.doc.data().price).toEqual(2); - expect(change.type).toEqual('modified'); - deleteThemAll(names, ref).then(done).catch(done.fail); - }); - - delayUpdate(ref, names[0], { price: 2 }); - }); - - it('should be able to filter snapshotChanges() types - added', async (done) => { - const ITEMS = 10; - const harness = await collectionHarness(afs, ITEMS); - const { randomCollectionName, ref, stocks } = harness; - let { names } = harness; - const nextId = ref.doc('a').id; - - const sub = stocks.snapshotChanges(['added']).pipe(skip(1)).subscribe(data => { - sub.unsubscribe(); - const change = data.filter(x => x.payload.doc.id === nextId)[0]; - expect(data.length).toEqual(ITEMS + 1); - expect(change.payload.doc.data().price).toEqual(2); - expect(change.type).toEqual('added'); - deleteThemAll(names, ref).then(done).catch(done.fail); - done(); - }); - - - names = names.concat([nextId]); - // TODO these two add tests are the only one really testing collection-group queries - // should flex more, maybe split the stocks between more than one collection - delayAdd(ref.doc(names[0]).collection(randomCollectionName), nextId, { price: 2 }); - }); - - it('should be able to filter snapshotChanges() types - added w/same id', async (done) => { - const ITEMS = 10; - const { randomCollectionName, ref, stocks, names } = await collectionHarness(afs, ITEMS); - - const sub = stocks.snapshotChanges(['added']).pipe(skip(1)).subscribe(data => { - sub.unsubscribe(); - const change = data.filter(x => x.payload.doc.id === names[0])[1]; - expect(data.length).toEqual(ITEMS + 1); - expect(change.payload.doc.data().price).toEqual(3); - expect(change.type).toEqual('added'); - ref.doc(names[0]).collection(randomCollectionName).doc(names[0]).delete() - .then(() => deleteThemAll(names, ref)) - .then(done).catch(done.fail); - done(); - }); - - delayAdd(ref.doc(names[0]).collection(randomCollectionName), names[0], { price: 3 }); - }); - - it('should be able to filter snapshotChanges() types - added/modified', async (done) => { - const ITEMS = 10; - - const harness = await collectionHarness(afs, ITEMS); - const { ref, stocks } = harness; - let { names } = harness; - - const nextId = ref.doc('a').id; - let count = 0; - - stocks.snapshotChanges(['added', 'modified']).pipe(skip(1), take(2)).subscribe(data => { - count += 1; - if (count === 1) { - const change = data.filter(x => x.payload.doc.id === nextId)[0]; - expect(data.length).toEqual(ITEMS + 1); - expect(change.payload.doc.data().price).toEqual(2); - expect(change.type).toEqual('added'); - delayUpdate(ref, names[0], { price: 2 }); - } - if (count === 2) { - const change = data.filter(x => x.payload.doc.id === names[0])[0]; - expect(data.length).toEqual(ITEMS + 1); - expect(change.payload.doc.data().price).toEqual(2); - expect(change.type).toEqual('modified'); - } - }).add(() => { - deleteThemAll(names, ref).then(done).catch(done.fail); - }); - - names = names.concat([nextId]); - delayAdd(ref, nextId, { price: 2 }); - }); - - it('should be able to filter snapshotChanges() types - removed', async (done) => { - const ITEMS = 10; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - - const sub = stocks.snapshotChanges(['added', 'removed']).pipe(skip(1)).subscribe(data => { - sub.unsubscribe(); - const change = data.filter(x => x.payload.doc.id === names[0]); - expect(data.length).toEqual(ITEMS - 1); - expect(change.length).toEqual(0); - deleteThemAll(names, ref).then(done).catch(done.fail); - done(); - }); - - delayDelete(ref, names[0], 400); - }); - - }); - - describe('stateChanges()', () => { - - it('should get stateChanges() updates', async (done: any) => { - const ITEMS = 10; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - - const sub = stocks.stateChanges().subscribe(data => { - // unsub immediately as we will be deleting data at the bottom - // and that will trigger another subscribe callback and fail - // the test - sub.unsubscribe(); - // We added ten things. This should be ten. - // This could not be ten if the batch failed or - // if the collection state is altered during a test run - expect(data.length).toEqual(ITEMS); - data.forEach(action => { - // We used the same piece of data so they should all equal - expect(action.payload.doc.data()).toEqual(FAKE_STOCK_DATA); - }); - deleteThemAll(names, ref).then(done).catch(done.fail); - }); - - }); - - it('should listen to all stateChanges() by default', async (done) => { - const ITEMS = 10; - let count = 0; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - stocks.stateChanges().subscribe(data => { - count = count + 1; - if (count === 1) { - ref.doc(names[0]).update({ price: 2 }); - } - if (count === 2) { - expect(data.length).toEqual(1); - expect(data[0].type).toEqual('modified'); - deleteThemAll(names, ref).then(done).catch(done.fail); - } - }); - }); - - it('should handle multiple subscriptions (hot)', async (done: any) => { - const ITEMS = 4; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - const changes = stocks.stateChanges(); - const sub = changes.subscribe(() => { - }).add( - changes.pipe(take(1)).subscribe(data => { - expect(data.length).toEqual(ITEMS); - sub.unsubscribe(); - }) - ).add(() => { - deleteThemAll(names, ref).then(done).catch(done.fail); - }); - }); - - it('should handle multiple subscriptions (warm)', async (done: any) => { - const ITEMS = 4; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - const changes = stocks.stateChanges(); - changes.pipe(take(1)).subscribe(() => { - }).add(() => { - changes.pipe(take(1)).subscribe(data => { - expect(data.length).toEqual(ITEMS); - }).add(() => { - deleteThemAll(names, ref).then(done).catch(done.fail); - }); - }); - }); - - it('should be able to filter stateChanges() types - modified', async (done) => { - const ITEMS = 10; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - - const sub = stocks.stateChanges(['modified']).subscribe(data => { - sub.unsubscribe(); - expect(data.length).toEqual(1); - expect(data[0].payload.doc.data().price).toEqual(2); - expect(data[0].type).toEqual('modified'); - deleteThemAll(names, ref).then(done).catch(done.fail); - done(); - }); - - delayUpdate(ref, names[0], { price: 2 }); - }); - - it('should be able to filter stateChanges() types - added', async (done) => { - const ITEMS = 10; - - const harness = await collectionHarness(afs, ITEMS); - const { ref, stocks } = harness; - let { names } = harness; - - - const sub = stocks.stateChanges(['added']).pipe(skip(1)).subscribe(data => { - sub.unsubscribe(); - expect(data.length).toEqual(1); - expect(data[0].payload.doc.data().price).toEqual(2); - expect(data[0].type).toEqual('added'); - deleteThemAll(names, ref).then(done).catch(done.fail); - done(); - }); - - const nextId = ref.doc('a').id; - names = names.concat([nextId]); - delayAdd(ref, nextId, { price: 2 }); - }); - - it('should be able to filter stateChanges() types - removed', async (done) => { - const ITEMS = 10; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - - const sub = stocks.stateChanges(['removed']).subscribe(data => { - sub.unsubscribe(); - expect(data.length).toEqual(1); - expect(data[0].type).toEqual('removed'); - deleteThemAll(names, ref).then(done).catch(done.fail); - done(); - }); - - delayDelete(ref, names[0], 400); - }); - }); - - describe('auditTrail()', () => { - it('should listen to all events for auditTrail() by default', async (done) => { - const ITEMS = 10; - let count = 0; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - const sub = stocks.auditTrail().subscribe(data => { - count = count + 1; - if (count === 1) { - ref.doc(names[0]).update({ price: 2 }); - } - if (count === 2) { - sub.unsubscribe(); - expect(data.length).toEqual(ITEMS + 1); - expect(data[data.length - 1].type).toEqual('modified'); - deleteThemAll(names, ref).then(done).catch(done.fail); - } - }); - }); - - it('should be able to filter auditTrail() types - removed', async (done) => { - const ITEMS = 10; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - - const sub = stocks.auditTrail(['removed']).subscribe(data => { - sub.unsubscribe(); - expect(data.length).toEqual(1); - expect(data[0].type).toEqual('removed'); - deleteThemAll(names, ref).then(done).catch(done.fail); - done(); - }); - - delayDelete(ref, names[0], 400); - }); - }); - -}); diff --git a/src/firestore/collection/changes.ts b/src/firestore/collection/changes.ts deleted file mode 100644 index 07f0d12ba..000000000 --- a/src/firestore/collection/changes.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { fromCollectionRef } from '../observable/fromRef'; -import { Observable, SchedulerLike } from 'rxjs'; -import { map, scan } from 'rxjs/operators'; - -import { DocumentChange, DocumentChangeAction, DocumentChangeType, Query } from '../interfaces'; - -/** - * Return a stream of document changes on a query. These results are not in sort order but in - * order of occurence. - */ -export function docChanges(query: Query, scheduler?: SchedulerLike): Observable[]> { - return fromCollectionRef(query, scheduler) - .pipe( - map(action => - action.payload.docChanges() - .map(change => ({ type: change.type, payload: change } as DocumentChangeAction)))); -} - -/** - * Return a stream of document changes on a query. These results are in sort order. - */ -export function sortedChanges( - query: Query, - events: DocumentChangeType[], - scheduler?: SchedulerLike): Observable[]> { - return fromCollectionRef(query, scheduler) - .pipe( - map(changes => changes.payload.docChanges()), - scan((current, changes) => combineChanges(current, changes, events), []), - map(changes => changes.map(c => ({ type: c.type, payload: c } as DocumentChangeAction)))); -} - -/** - * Combines the total result set from the current set of changes from an incoming set - * of changes. - */ -export function combineChanges(current: DocumentChange[], changes: DocumentChange[], events: DocumentChangeType[]) { - changes.forEach(change => { - // skip unwanted change types - if (events.indexOf(change.type) > -1) { - current = combineChange(current, change); - } - }); - return current; -} - -/** - * Creates a new sorted array from a new change. - */ -export function combineChange(combined: DocumentChange[], change: DocumentChange): DocumentChange[] { - switch (change.type) { - case 'added': - if (combined[change.newIndex] && combined[change.newIndex].doc.ref.isEqual(change.doc.ref)) { - // Not sure why the duplicates are getting fired - } else { - combined.splice(change.newIndex, 0, change); - } - break; - case 'modified': - if (combined[change.oldIndex] == null || combined[change.oldIndex].doc.ref.isEqual(change.doc.ref)) { - // When an item changes position we first remove it - // and then add it's new position - if (change.oldIndex !== change.newIndex) { - combined.splice(change.oldIndex, 1); - combined.splice(change.newIndex, 0, change); - } else { - combined.splice(change.newIndex, 1, change); - } - } - break; - case 'removed': - if (combined[change.oldIndex] && combined[change.oldIndex].doc.ref.isEqual(change.doc.ref)) { - combined.splice(change.oldIndex, 1); - } - break; - } - return combined; -} diff --git a/src/firestore/collection/collection.spec.ts b/src/firestore/collection/collection.spec.ts deleted file mode 100644 index 5d1951a59..000000000 --- a/src/firestore/collection/collection.spec.ts +++ /dev/null @@ -1,484 +0,0 @@ -import { AngularFireModule, FirebaseApp } from '@angular/fire'; -import { AngularFirestore, SETTINGS } from '../firestore'; -import { AngularFirestoreModule } from '../firestore.module'; -import { AngularFirestoreCollection } from './collection'; -import { QueryFn } from '../interfaces'; -import { BehaviorSubject } from 'rxjs'; -import { skip, switchMap, take } from 'rxjs/operators'; -import 'firebase/firestore'; - -import { TestBed } from '@angular/core/testing'; -import { COMMON_CONFIG } from '../../test-config'; - -import { - createRandomStocks, - delayAdd, - delayDelete, - delayUpdate, - deleteThemAll, - FAKE_STOCK_DATA, - rando, - randomName, - Stock -} from '../utils.spec'; - -async function collectionHarness(afs: AngularFirestore, items: number, queryFn?: QueryFn) { - const randomCollectionName = randomName(afs.firestore); - const ref = afs.firestore.collection(`${randomCollectionName}`); - if (!queryFn) { - queryFn = (ref) => ref; - } - const stocks = new AngularFirestoreCollection(ref, queryFn(ref), afs); - const names = await createRandomStocks(afs.firestore, ref, items); - return { randomCollectionName, ref, stocks, names }; -} - -describe('AngularFirestoreCollection', () => { - let app: FirebaseApp; - let afs: AngularFirestore; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - AngularFireModule.initializeApp(COMMON_CONFIG, rando()), - AngularFirestoreModule - ], - providers: [ - { provide: SETTINGS, useValue: { host: 'localhost:8080', ssl: false } } - ] - }); - - app = TestBed.inject(FirebaseApp); - afs = TestBed.inject(AngularFirestore); - }); - - afterEach(() => { - app.delete(); - }); - - describe('valueChanges()', () => { - - it('should get unwrapped snapshot', async (done: any) => { - const ITEMS = 4; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - - const sub = stocks.valueChanges().subscribe(data => { - // unsub immediately as we will be deleting data at the bottom - // and that will trigger another subscribe callback and fail - // the test - sub.unsubscribe(); - // We added four things. This should be four. - // This could not be four if the batch failed or - // if the collection state is altered during a test run - expect(data.length).toEqual(ITEMS); - data.forEach(stock => { - // We used the same piece of data so they should all equal - expect(stock).toEqual(FAKE_STOCK_DATA); - }); - // Delete them all - const promises = names.map(name => ref.doc(name).delete()); - Promise.all(promises).then(done).catch(fail); - }); - - }); - - /* FLAKE? timing out - it('should optionally map the doc ID to the emitted data object', async (done: any) => { - const ITEMS = 1; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - const idField = 'myCustomID'; - const sub = stocks.valueChanges({idField}).subscribe(data => { - sub.unsubscribe(); - const stock = data[0]; - expect(stock[idField]).toBeDefined(); - expect(stock).toEqual(jasmine.objectContaining(FAKE_STOCK_DATA)); - deleteThemAll(names, ref).then(done).catch(fail); - }) - });*/ - - it('should handle multiple subscriptions (hot)', async (done: any) => { - const ITEMS = 4; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - const changes = stocks.valueChanges(); - const sub = changes.subscribe(() => { - }).add( - changes.pipe(take(1)).subscribe(data => { - expect(data.length).toEqual(ITEMS); - sub.unsubscribe(); - }) - ).add(() => { - deleteThemAll(names, ref).then(done).catch(done.fail); - }); - }); - - it('should handle multiple subscriptions (warm)', async (done: any) => { - const ITEMS = 4; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - const changes = stocks.valueChanges(); - changes.pipe(take(1)).subscribe(() => { - }).add(() => { - changes.pipe(take(1)).subscribe(data => { - expect(data.length).toEqual(ITEMS); - }).add(() => { - deleteThemAll(names, ref).then(done).catch(done.fail); - }); - }); - }); - - it('should handle dynamic queries that return empty sets', async (done) => { - const ITEMS = 10; - let count = 0; - - const pricefilter$ = new BehaviorSubject(null); - const randomCollectionName = randomName(afs.firestore); - const ref = afs.firestore.collection(`${randomCollectionName}`); - const names = await createRandomStocks(afs.firestore, ref, ITEMS); - const sub = pricefilter$.pipe(switchMap(price => { - return afs.collection(randomCollectionName, ref => price ? ref.where('price', '==', price) : ref).valueChanges(); - })).subscribe(data => { - count = count + 1; - // the first time should all be 'added' - if (count === 1) { - expect(data.length).toEqual(ITEMS); - pricefilter$.next(-1); - } - // on the second round, we should have filtered out everything - if (count === 2) { - expect(data.length).toEqual(0); - sub.unsubscribe(); - deleteThemAll(names, ref).then(done).catch(done.fail); - } - }); - }); - - }); - - describe('snapshotChanges()', () => { - - it('should listen to all snapshotChanges() by default', async (done) => { - const ITEMS = 10; - let count = 0; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - const sub = stocks.snapshotChanges().subscribe(data => { - count = count + 1; - // the first time should all be 'added' - if (count === 1) { - // make an update - stocks.doc(names[0]).update({ price: 2 }); - } - // on the second round, make sure the array is still the same - // length but the updated item is now modified - if (count === 2) { - expect(data.length).toEqual(ITEMS); - const change = data.filter(x => x.payload.doc.id === names[0])[0]; - expect(change.type).toEqual('modified'); - sub.unsubscribe(); - deleteThemAll(names, ref).then(done).catch(done.fail); - } - }); - }); - - it('should handle multiple subscriptions (hot)', async (done: any) => { - const ITEMS = 4; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - const changes = stocks.snapshotChanges(); - const sub = changes.subscribe(() => { - }).add( - changes.pipe(take(1)).subscribe(data => { - expect(data.length).toEqual(ITEMS); - sub.unsubscribe(); - }) - ).add(() => { - deleteThemAll(names, ref).then(done).catch(done.fail); - }); - }); - - it('should handle multiple subscriptions (warm)', async (done: any) => { - const ITEMS = 4; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - const changes = stocks.snapshotChanges(); - changes.pipe(take(1)).subscribe(() => { - }).add(() => { - changes.pipe(take(1)).subscribe(data => { - expect(data.length).toEqual(ITEMS); - }).add(() => { - deleteThemAll(names, ref).then(done).catch(done.fail); - }); - }); - }); - - it('should update order on queries', async (done) => { - const ITEMS = 10; - let count = 0; - let firstIndex = 0; - const { ref, stocks, names } = - await collectionHarness(afs, ITEMS, ref => ref.orderBy('price', 'desc')); - const sub = stocks.snapshotChanges().subscribe(data => { - count = count + 1; - // the first time should all be 'added' - if (count === 1) { - // make an update - firstIndex = data.filter(d => d.payload.doc.id === names[0])[0].payload.newIndex; - stocks.doc(names[0]).update({ price: 2 }); - } - // on the second round, make sure the array is still the same - // length but the updated item is now modified - if (count === 2) { - expect(data.length).toEqual(ITEMS); - const change = data.filter(x => x.payload.doc.id === names[0])[0]; - expect(change.type).toEqual('modified'); - expect(change.payload.oldIndex).toEqual(firstIndex); - sub.unsubscribe(); - deleteThemAll(names, ref).then(done).catch(done.fail); - } - }); - }); - - it('should be able to filter snapshotChanges() types - modified', async (done) => { - const ITEMS = 10; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - - const sub = stocks.snapshotChanges(['modified']).pipe(skip(1)).subscribe(data => { - sub.unsubscribe(); - const change = data.filter(x => x.payload.doc.id === names[0])[0]; - expect(data.length).toEqual(1); - expect(change.payload.doc.data().price).toEqual(2); - expect(change.type).toEqual('modified'); - deleteThemAll(names, ref).then(done).catch(done.fail); - }); - - delayUpdate(stocks, names[0], { price: 2 }); - }); - - it('should be able to filter snapshotChanges() types - added', async (done) => { - const ITEMS = 10; - const harness = await collectionHarness(afs, ITEMS); - const { ref, stocks } = harness; - let names = harness.names; - - const nextId = ref.doc('a').id; - - const sub = stocks.snapshotChanges(['added']).pipe(skip(1)).subscribe(data => { - sub.unsubscribe(); - const change = data.filter(x => x.payload.doc.id === nextId)[0]; - expect(data.length).toEqual(ITEMS + 1); - expect(change.payload.doc.data().price).toEqual(2); - expect(change.type).toEqual('added'); - deleteThemAll(names, ref).then(done).catch(done.fail); - done(); - }); - - - names = names.concat([nextId]); - delayAdd(stocks, nextId, { price: 2 }); - }); - - it('should be able to filter snapshotChanges() types - added/modified', async (done) => { - const ITEMS = 10; - const harness = await collectionHarness(afs, ITEMS); - const { ref, stocks } = harness; - let names = harness.names; - - const nextId = ref.doc('a').id; - let count = 0; - - stocks.snapshotChanges(['added', 'modified']).pipe(skip(1), take(2)).subscribe(data => { - count += 1; - if (count === 1) { - const change = data.filter(x => x.payload.doc.id === nextId)[0]; - expect(data.length).toEqual(ITEMS + 1); - expect(change.payload.doc.data().price).toEqual(2); - expect(change.type).toEqual('added'); - delayUpdate(stocks, names[0], { price: 2 }); - } - if (count === 2) { - const change = data.filter(x => x.payload.doc.id === names[0])[0]; - expect(data.length).toEqual(ITEMS + 1); - expect(change.payload.doc.data().price).toEqual(2); - expect(change.type).toEqual('modified'); - } - }).add(() => { - deleteThemAll(names, ref).then(done).catch(done.fail); - }); - - names = names.concat([nextId]); - delayAdd(stocks, nextId, { price: 2 }); - }); - - it('should be able to filter snapshotChanges() types - removed', async (done) => { - const ITEMS = 10; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - - const sub = stocks.snapshotChanges(['added', 'removed']).pipe(skip(1)).subscribe(data => { - sub.unsubscribe(); - const change = data.filter(x => x.payload.doc.id === names[0]); - expect(data.length).toEqual(ITEMS - 1); - expect(change.length).toEqual(0); - deleteThemAll(names, ref).then(done).catch(done.fail); - done(); - }); - - delayDelete(stocks, names[0], 400); - }); - - }); - - describe('stateChanges()', () => { - - it('should get stateChanges() updates', async (done: any) => { - const ITEMS = 10; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - - const sub = stocks.stateChanges().subscribe(data => { - // unsub immediately as we will be deleting data at the bottom - // and that will trigger another subscribe callback and fail - // the test - sub.unsubscribe(); - // We added ten things. This should be ten. - // This could not be ten if the batch failed or - // if the collection state is altered during a test run - expect(data.length).toEqual(ITEMS); - data.forEach(action => { - // We used the same piece of data so they should all equal - expect(action.payload.doc.data()).toEqual(FAKE_STOCK_DATA); - }); - deleteThemAll(names, ref).then(done).catch(done.fail); - }); - - }); - - it('should listen to all stateChanges() by default', async (done) => { - const ITEMS = 10; - let count = 0; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - stocks.stateChanges().subscribe(data => { - count = count + 1; - if (count === 1) { - stocks.doc(names[0]).update({ price: 2 }); - } - if (count === 2) { - expect(data.length).toEqual(1); - expect(data[0].type).toEqual('modified'); - deleteThemAll(names, ref).then(done).catch(done.fail); - } - }); - }); - - it('should handle multiple subscriptions (hot)', async (done: any) => { - const ITEMS = 4; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - const changes = stocks.stateChanges(); - const sub = changes.subscribe(() => { - }).add( - changes.pipe(take(1)).subscribe(data => { - expect(data.length).toEqual(ITEMS); - sub.unsubscribe(); - }) - ).add(() => { - deleteThemAll(names, ref).then(done).catch(done.fail); - }); - }); - - it('should handle multiple subscriptions (warm)', async (done: any) => { - const ITEMS = 4; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - const changes = stocks.stateChanges(); - changes.pipe(take(1)).subscribe(() => { - }).add(() => { - changes.pipe(take(1)).subscribe(data => { - expect(data.length).toEqual(ITEMS); - }).add(() => { - deleteThemAll(names, ref).then(done).catch(done.fail); - }); - }); - }); - - it('should be able to filter stateChanges() types - modified', async (done) => { - const ITEMS = 10; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - - const sub = stocks.stateChanges(['modified']).subscribe(data => { - sub.unsubscribe(); - expect(data.length).toEqual(1); - expect(data[0].payload.doc.data().price).toEqual(2); - expect(data[0].type).toEqual('modified'); - deleteThemAll(names, ref).then(done).catch(done.fail); - done(); - }); - - delayUpdate(stocks, names[0], { price: 2 }); - }); - - it('should be able to filter stateChanges() types - added', async (done) => { - const ITEMS = 10; - - const harness = await collectionHarness(afs, ITEMS); - const { ref, stocks } = harness; - let names = harness.names; - - const sub = stocks.stateChanges(['added']).pipe(skip(1)).subscribe(data => { - sub.unsubscribe(); - expect(data.length).toEqual(1); - expect(data[0].payload.doc.data().price).toEqual(2); - expect(data[0].type).toEqual('added'); - deleteThemAll(names, ref).then(done).catch(done.fail); - done(); - }); - - const nextId = ref.doc('a').id; - names = names.concat([nextId]); - delayAdd(stocks, nextId, { price: 2 }); - }); - - it('should be able to filter stateChanges() types - removed', async (done) => { - const ITEMS = 10; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - - const sub = stocks.stateChanges(['removed']).subscribe(data => { - sub.unsubscribe(); - expect(data.length).toEqual(1); - expect(data[0].type).toEqual('removed'); - deleteThemAll(names, ref).then(done).catch(done.fail); - done(); - }); - - delayDelete(stocks, names[0], 400); - }); - }); - - describe('auditTrail()', () => { - it('should listen to all events for auditTrail() by default', async (done) => { - const ITEMS = 10; - let count = 0; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - const sub = stocks.auditTrail().subscribe(data => { - count = count + 1; - if (count === 1) { - stocks.doc(names[0]).update({ price: 2 }); - } - if (count === 2) { - sub.unsubscribe(); - expect(data.length).toEqual(ITEMS + 1); - expect(data[data.length - 1].type).toEqual('modified'); - deleteThemAll(names, ref).then(done).catch(done.fail); - } - }); - }); - - it('should be able to filter auditTrail() types - removed', async (done) => { - const ITEMS = 10; - const { ref, stocks, names } = await collectionHarness(afs, ITEMS); - - const sub = stocks.auditTrail(['removed']).subscribe(data => { - sub.unsubscribe(); - expect(data.length).toEqual(1); - expect(data[0].type).toEqual('removed'); - deleteThemAll(names, ref).then(done).catch(done.fail); - done(); - }); - - delayDelete(stocks, names[0], 400); - }); - }); - -}); diff --git a/src/firestore/document/document.spec.ts b/src/firestore/document/document.spec.ts deleted file mode 100644 index a6acc0b52..000000000 --- a/src/firestore/document/document.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { AngularFireModule, FirebaseApp } from '@angular/fire'; -import { AngularFirestore, SETTINGS } from '../firestore'; -import { AngularFirestoreModule } from '../firestore.module'; -import { AngularFirestoreDocument } from './document'; -import { take } from 'rxjs/operators'; - -import { TestBed } from '@angular/core/testing'; -import { COMMON_CONFIG } from '../../test-config'; - -import { FAKE_STOCK_DATA, rando, randomName, Stock } from '../utils.spec'; -import 'firebase/firestore'; - -describe('AngularFirestoreDocument', () => { - let app: FirebaseApp; - let afs: AngularFirestore; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - AngularFireModule.initializeApp(COMMON_CONFIG, rando()), - AngularFirestoreModule - ], - providers: [ - { provide: SETTINGS, useValue: { host: 'localhost:8080', ssl: false } } - ] - }); - - app = TestBed.inject(FirebaseApp); - afs = TestBed.inject(AngularFirestore); - }); - - afterEach(() => { - app.delete(); - }); - - it('should get action updates', async (done: any) => { - const randomCollectionName = randomName(afs.firestore); - const ref = afs.firestore.doc(`${randomCollectionName}/FAKE`); - const stock = new AngularFirestoreDocument(ref, afs); - await stock.set(FAKE_STOCK_DATA); - const sub = stock - .snapshotChanges() - .subscribe(async a => { - sub.unsubscribe(); - if (a.payload.exists) { - expect(a.payload.data()).toEqual(FAKE_STOCK_DATA); - stock.delete().then(done).catch(done.fail); - } - }); - }); - - it('should get unwrapped snapshot', async (done: any) => { - const randomCollectionName = afs.firestore.collection('a').doc().id; - const ref = afs.firestore.doc(`${randomCollectionName}/FAKE`); - const stock = new AngularFirestoreDocument(ref, afs); - await stock.set(FAKE_STOCK_DATA); - const obs$ = stock.valueChanges(); - obs$.pipe(take(1)).subscribe(async data => { - expect(data).toEqual(FAKE_STOCK_DATA); - stock.delete().then(done).catch(done.fail); - }); - }); - -}); diff --git a/src/firestore/firebase.ts b/src/firestore/firebase.ts new file mode 100644 index 000000000..7b61867fc --- /dev/null +++ b/src/firestore/firebase.ts @@ -0,0 +1,128 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +export * from 'firebase/firestore'; +import { ÉĩzoneWrap } from '@angular/fire'; +import { + addDoc as _addDoc, + aggregateFieldEqual as _aggregateFieldEqual, + aggregateQuerySnapshotEqual as _aggregateQuerySnapshotEqual, + and as _and, + clearIndexedDbPersistence as _clearIndexedDbPersistence, + collection as _collection, + collectionGroup as _collectionGroup, + connectFirestoreEmulator as _connectFirestoreEmulator, + deleteAllPersistentCacheIndexes as _deleteAllPersistentCacheIndexes, + deleteDoc as _deleteDoc, + deleteField as _deleteField, + disableNetwork as _disableNetwork, + disablePersistentCacheIndexAutoCreation as _disablePersistentCacheIndexAutoCreation, + doc as _doc, + documentId as _documentId, + documentSnapshotFromJSON as _documentSnapshotFromJSON, + enableIndexedDbPersistence as _enableIndexedDbPersistence, + enableMultiTabIndexedDbPersistence as _enableMultiTabIndexedDbPersistence, + enableNetwork as _enableNetwork, + enablePersistentCacheIndexAutoCreation as _enablePersistentCacheIndexAutoCreation, + endAt as _endAt, + endBefore as _endBefore, + getAggregateFromServer as _getAggregateFromServer, + getCountFromServer as _getCountFromServer, + getDoc as _getDoc, + getDocFromCache as _getDocFromCache, + getDocFromServer as _getDocFromServer, + getDocs as _getDocs, + getDocsFromCache as _getDocsFromCache, + getDocsFromServer as _getDocsFromServer, + getFirestore as _getFirestore, + getPersistentCacheIndexManager as _getPersistentCacheIndexManager, + increment as _increment, + initializeFirestore as _initializeFirestore, + limit as _limit, + limitToLast as _limitToLast, + loadBundle as _loadBundle, + namedQuery as _namedQuery, + onSnapshot as _onSnapshot, + onSnapshotResume as _onSnapshotResume, + onSnapshotsInSync as _onSnapshotsInSync, + or as _or, + orderBy as _orderBy, + query as _query, + queryEqual as _queryEqual, + querySnapshotFromJSON as _querySnapshotFromJSON, + refEqual as _refEqual, + runTransaction as _runTransaction, + setDoc as _setDoc, + setIndexConfiguration as _setIndexConfiguration, + setLogLevel as _setLogLevel, + snapshotEqual as _snapshotEqual, + startAfter as _startAfter, + startAt as _startAt, + sum as _sum, + terminate as _terminate, + updateDoc as _updateDoc, + vector as _vector, + waitForPendingWrites as _waitForPendingWrites, + where as _where, + writeBatch as _writeBatch +} from 'firebase/firestore'; + +export const addDoc = ÉĩzoneWrap(_addDoc, true, 2); +export const aggregateFieldEqual = ÉĩzoneWrap(_aggregateFieldEqual, true, 2); +export const aggregateQuerySnapshotEqual = ÉĩzoneWrap(_aggregateQuerySnapshotEqual, true, 2); +export const and = ÉĩzoneWrap(_and, true, 2); +export const clearIndexedDbPersistence = ÉĩzoneWrap(_clearIndexedDbPersistence, true); +export const collection = ÉĩzoneWrap(_collection, true, 2); +export const collectionGroup = ÉĩzoneWrap(_collectionGroup, true, 2); +export const connectFirestoreEmulator = ÉĩzoneWrap(_connectFirestoreEmulator, true); +export const deleteAllPersistentCacheIndexes = ÉĩzoneWrap(_deleteAllPersistentCacheIndexes, true); +export const deleteDoc = ÉĩzoneWrap(_deleteDoc, true, 2); +export const deleteField = ÉĩzoneWrap(_deleteField, true, 2); +export const disableNetwork = ÉĩzoneWrap(_disableNetwork, true); +export const disablePersistentCacheIndexAutoCreation = ÉĩzoneWrap(_disablePersistentCacheIndexAutoCreation, true); +export const doc = ÉĩzoneWrap(_doc, true, 2); +export const documentId = ÉĩzoneWrap(_documentId, true, 2); +export const documentSnapshotFromJSON = ÉĩzoneWrap(_documentSnapshotFromJSON, true); +export const enableIndexedDbPersistence = ÉĩzoneWrap(_enableIndexedDbPersistence, true); +export const enableMultiTabIndexedDbPersistence = ÉĩzoneWrap(_enableMultiTabIndexedDbPersistence, true); +export const enableNetwork = ÉĩzoneWrap(_enableNetwork, true); +export const enablePersistentCacheIndexAutoCreation = ÉĩzoneWrap(_enablePersistentCacheIndexAutoCreation, true); +export const endAt = ÉĩzoneWrap(_endAt, true, 2); +export const endBefore = ÉĩzoneWrap(_endBefore, true, 2); +export const getAggregateFromServer = ÉĩzoneWrap(_getAggregateFromServer, true); +export const getCountFromServer = ÉĩzoneWrap(_getCountFromServer, true); +export const getDoc = ÉĩzoneWrap(_getDoc, true); +export const getDocFromCache = ÉĩzoneWrap(_getDocFromCache, true); +export const getDocFromServer = ÉĩzoneWrap(_getDocFromServer, true); +export const getDocs = ÉĩzoneWrap(_getDocs, true); +export const getDocsFromCache = ÉĩzoneWrap(_getDocsFromCache, true); +export const getDocsFromServer = ÉĩzoneWrap(_getDocsFromServer, true); +export const getFirestore = ÉĩzoneWrap(_getFirestore, true); +export const getPersistentCacheIndexManager = ÉĩzoneWrap(_getPersistentCacheIndexManager, true); +export const increment = ÉĩzoneWrap(_increment, true, 2); +export const initializeFirestore = ÉĩzoneWrap(_initializeFirestore, true); +export const limit = ÉĩzoneWrap(_limit, true, 2); +export const limitToLast = ÉĩzoneWrap(_limitToLast, true, 2); +export const loadBundle = ÉĩzoneWrap(_loadBundle, true); +export const namedQuery = ÉĩzoneWrap(_namedQuery, true, 2); +export const onSnapshot = ÉĩzoneWrap(_onSnapshot, true); +export const onSnapshotResume = ÉĩzoneWrap(_onSnapshotResume, true); +export const onSnapshotsInSync = ÉĩzoneWrap(_onSnapshotsInSync, true); +export const or = ÉĩzoneWrap(_or, true, 2); +export const orderBy = ÉĩzoneWrap(_orderBy, true, 2); +export const query = ÉĩzoneWrap(_query, true, 2); +export const queryEqual = ÉĩzoneWrap(_queryEqual, true, 2); +export const querySnapshotFromJSON = ÉĩzoneWrap(_querySnapshotFromJSON, true); +export const refEqual = ÉĩzoneWrap(_refEqual, true, 2); +export const runTransaction = ÉĩzoneWrap(_runTransaction, true); +export const setDoc = ÉĩzoneWrap(_setDoc, true, 2); +export const setIndexConfiguration = ÉĩzoneWrap(_setIndexConfiguration, true); +export const setLogLevel = ÉĩzoneWrap(_setLogLevel, true); +export const snapshotEqual = ÉĩzoneWrap(_snapshotEqual, true, 2); +export const startAfter = ÉĩzoneWrap(_startAfter, true, 2); +export const startAt = ÉĩzoneWrap(_startAt, true, 2); +export const sum = ÉĩzoneWrap(_sum, true, 2); +export const terminate = ÉĩzoneWrap(_terminate, true); +export const updateDoc = ÉĩzoneWrap(_updateDoc, true, 2); +export const vector = ÉĩzoneWrap(_vector, true, 2); +export const waitForPendingWrites = ÉĩzoneWrap(_waitForPendingWrites, true); +export const where = ÉĩzoneWrap(_where, true, 2); +export const writeBatch = ÉĩzoneWrap(_writeBatch, true, 2); diff --git a/src/firestore/firestore.module.ts b/src/firestore/firestore.module.ts index f770fc38c..4946af42e 100644 --- a/src/firestore/firestore.module.ts +++ b/src/firestore/firestore.module.ts @@ -1,21 +1,82 @@ -import { ModuleWithProviders, NgModule } from '@angular/core'; -import { PersistenceSettings } from './interfaces'; -import { AngularFirestore, ENABLE_PERSISTENCE, PERSISTENCE_SETTINGS } from './firestore'; +import { + EnvironmentProviders, + InjectionToken, + Injector, + NgModule, + NgZone, + Optional, + makeEnvironmentProviders, +} from '@angular/core'; +import { VERSION, ÉĩAngularFireSchedulers, ÉĩgetDefaultInstanceOf } from '@angular/fire'; +import { FirebaseApp, FirebaseApps } from '@angular/fire/app'; +import { AppCheckInstances } from '@angular/fire/app-check'; +import { AuthInstances } from '@angular/fire/auth'; +import { registerVersion } from 'firebase/app'; +import { Firestore as FirebaseFirestore } from 'firebase/firestore'; +import { FIRESTORE_PROVIDER_NAME, Firestore, FirestoreInstances } from './firestore'; + +export const PROVIDED_FIRESTORE_INSTANCES = new InjectionToken('angularfire2.firestore-instances'); + +export function defaultFirestoreInstanceFactory(provided: FirebaseFirestore[]|undefined, defaultApp: FirebaseApp) { + const defaultFirestore = ÉĩgetDefaultInstanceOf(FIRESTORE_PROVIDER_NAME, provided, defaultApp); + return defaultFirestore && new Firestore(defaultFirestore); +} + +export function firestoreInstanceFactory(fn: (injector: Injector) => FirebaseFirestore) { + return (zone: NgZone, injector: Injector) => { + const firestore = zone.runOutsideAngular(() => fn(injector)); + return new Firestore(firestore); + }; +} + +const FIRESTORE_INSTANCES_PROVIDER = { + provide: FirestoreInstances, + deps: [ + [new Optional(), PROVIDED_FIRESTORE_INSTANCES ], + ] +}; + +const DEFAULT_FIRESTORE_INSTANCE_PROVIDER = { + provide: Firestore, + useFactory: defaultFirestoreInstanceFactory, + deps: [ + [new Optional(), PROVIDED_FIRESTORE_INSTANCES ], + FirebaseApp, + ] +}; @NgModule({ - providers: [ AngularFirestore ] + providers: [ + DEFAULT_FIRESTORE_INSTANCE_PROVIDER, + FIRESTORE_INSTANCES_PROVIDER, + ] }) -export class AngularFirestoreModule { - /** - * Attempt to enable persistent storage, if possible - */ - static enablePersistence(persistenceSettings?: PersistenceSettings): ModuleWithProviders { - return { - ngModule: AngularFirestoreModule, - providers: [ - { provide: ENABLE_PERSISTENCE, useValue: true }, - { provide: PERSISTENCE_SETTINGS, useValue: persistenceSettings }, - ] - }; +export class FirestoreModule { + constructor() { + registerVersion('angularfire', VERSION.full, 'fst'); } } + +export function provideFirestore(fn: (injector: Injector) => FirebaseFirestore, ...deps: any[]): EnvironmentProviders { + registerVersion('angularfire', VERSION.full, 'fst'); + + return makeEnvironmentProviders([ + DEFAULT_FIRESTORE_INSTANCE_PROVIDER, + FIRESTORE_INSTANCES_PROVIDER, + { + provide: PROVIDED_FIRESTORE_INSTANCES, + useFactory: firestoreInstanceFactory(fn), + multi: true, + deps: [ + NgZone, + Injector, + ÉĩAngularFireSchedulers, + FirebaseApps, + // Firestore+Auth work better if Auth is loaded first + [new Optional(), AuthInstances ], + [new Optional(), AppCheckInstances ], + ...deps, + ] + } + ]); +} diff --git a/src/firestore/firestore.spec.ts b/src/firestore/firestore.spec.ts index be957cd4b..71d452968 100644 --- a/src/firestore/firestore.spec.ts +++ b/src/firestore/firestore.spec.ts @@ -1,175 +1,39 @@ -import { AngularFireModule, FIREBASE_APP_NAME, FIREBASE_OPTIONS, FirebaseApp } from '@angular/fire'; -import { AngularFirestore, SETTINGS } from './firestore'; -import { AngularFirestoreModule } from './firestore.module'; -import { AngularFirestoreDocument } from './document/document'; -import { AngularFirestoreCollection } from './collection/collection'; - import { TestBed } from '@angular/core/testing'; -import { COMMON_CONFIG } from '../test-config'; -import 'firebase/firestore'; -import { rando } from './utils.spec'; - -describe('AngularFirestore', () => { - let app: FirebaseApp; - let afs: AngularFirestore; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - AngularFireModule.initializeApp(COMMON_CONFIG, rando()), - AngularFirestoreModule.enablePersistence() - ], - providers: [ - { provide: SETTINGS, useValue: { host: 'localhost:8080', ssl: false } } - ] - }); - - app = TestBed.inject(FirebaseApp); - afs = TestBed.inject(AngularFirestore); - }); - - afterEach(() => { - app.delete(); - }); - - it('should be the properly initialized type', () => { - expect(afs instanceof AngularFirestore).toBe(true); - }); - - it('should have an initialized Firebase app', () => { - expect(afs.firestore.app).toBeDefined(); - }); - - it('should create an AngularFirestoreDocument from a string path', () => { - const doc = afs.doc('a/doc'); - expect(doc instanceof AngularFirestoreDocument).toBe(true); - }); - - it('should create an AngularFirestoreDocument from a string path', () => { - const doc = afs.doc(afs.doc('a/doc').ref); - expect(doc instanceof AngularFirestoreDocument).toBe(true); - }); - - it('should create an AngularFirestoreCollection from a string path', () => { - const collection = afs.collection('stuffs'); - expect(collection instanceof AngularFirestoreCollection).toBe(true); - }); - - it('should create an AngularFirestoreCollection from a reference', () => { - const collection = afs.collection(afs.collection('stuffs').ref); - expect(collection instanceof AngularFirestoreCollection).toBe(true); - }); - - it('should throw on an invalid document path', () => { - const singleWrapper = () => afs.doc('collection'); - const tripleWrapper = () => afs.doc('collection/doc/subcollection'); - expect(singleWrapper).toThrowError(); - expect(tripleWrapper).toThrowError(); - }); - - it('should throw on an invalid collection path', () => { - const singleWrapper = () => afs.collection('collection/doc'); - const quadWrapper = () => afs.collection('collection/doc/subcollection/doc'); - expect(singleWrapper).toThrowError(); - expect(quadWrapper).toThrowError(); - }); - - if (typeof window === 'undefined') { - - it('should not enable persistence (Node.js)', (done) => { - afs.persistenceEnabled$.subscribe(isEnabled => { - expect(isEnabled).toBe(false); - done(); - }); - }); - - } else { - - it('should enable persistence', (done) => { - afs.persistenceEnabled$.subscribe(isEnabled => { - expect(isEnabled).toBe(true); - done(); - }); - }); - - } - -}); - -describe('AngularFirestore with different app', () => { - let app: FirebaseApp; - let afs: AngularFirestore; - let firebaseAppName: string; - - beforeEach(() => { - firebaseAppName = rando(); - TestBed.configureTestingModule({ - imports: [ - AngularFireModule.initializeApp(COMMON_CONFIG, rando()), - AngularFirestoreModule - ], - providers: [ - { provide: FIREBASE_APP_NAME, useValue: firebaseAppName }, - { provide: FIREBASE_OPTIONS, useValue: COMMON_CONFIG }, - { provide: SETTINGS, useValue: { host: 'localhost:8080', ssl: false } } - ] - }); - - - app = TestBed.inject(FirebaseApp); - afs = TestBed.inject(AngularFirestore); - }); - - afterEach(() => { - app.delete(); - }); - - describe('', () => { - - it('should be an AngularFirestore type', () => { - expect(afs instanceof AngularFirestore).toEqual(true); - }); - - it('should have an initialized Firebase app', () => { - expect(afs.firestore.app).toBeDefined(); - }); - - it('should have an initialized Firebase app instance member', () => { - expect(afs.firestore.app.name).toEqual(firebaseAppName); - }); - }); - -}); +import { FirebaseApp, getApp, initializeApp, provideFirebaseApp } from '@angular/fire/app'; +import { Firestore, connectFirestoreEmulator, getFirestore, provideFirestore } from '@angular/fire/firestore'; +import { COMMON_CONFIG, firestoreEmulatorPort } from '../test-config'; +import { rando } from '../utils'; - -describe('AngularFirestore without persistance', () => { +describe('Firestore', () => { let app: FirebaseApp; - let afs: AngularFirestore; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - AngularFireModule.initializeApp(COMMON_CONFIG, rando()), - AngularFirestoreModule - ], - providers: [ - { provide: SETTINGS, useValue: { host: 'localhost:8080', ssl: false } } - ] + let firestore: Firestore; + let providedFirestore: Firestore; + let appName: string; + + describe('single injection', () => { + + beforeEach(() => { + appName = rando(); + TestBed.configureTestingModule({ + providers: [ + provideFirebaseApp(() => initializeApp(COMMON_CONFIG, appName)), + provideFirestore(() => { + providedFirestore = getFirestore(getApp(appName)); + connectFirestoreEmulator(providedFirestore, 'localhost', firestoreEmulatorPort); + return providedFirestore; + }), + ], + }); + app = TestBed.inject(FirebaseApp); + firestore = TestBed.inject(Firestore); + }); + + it('should be injectable', () => { + expect(providedFirestore).toBeTruthy(); + expect(firestore).toEqual(providedFirestore); + expect(firestore.app).toEqual(app); }); - app = TestBed.inject(FirebaseApp); - afs = TestBed.inject(AngularFirestore); - }); - - afterEach(() => { - app.delete(); - }); - - it('should not enable persistence', (done) => { - afs.persistenceEnabled$.subscribe(isEnabled => { - expect(isEnabled).toBe(false); - done(); - }); }); }); diff --git a/src/firestore/firestore.ts b/src/firestore/firestore.ts index f7d1571e6..6a9f547d2 100644 --- a/src/firestore/firestore.ts +++ b/src/firestore/firestore.ts @@ -1,226 +1,30 @@ -import { Inject, Injectable, InjectionToken, NgZone, Optional, PLATFORM_ID } from '@angular/core'; -import { from, Observable, of } from 'rxjs'; -import { - AssociatedReference, - CollectionReference, - DocumentReference, - PersistenceSettings, - Query, - QueryFn, - QueryGroupFn, - Settings -} from './interfaces'; -import { AngularFirestoreDocument } from './document/document'; -import { AngularFirestoreCollection } from './collection/collection'; -import { AngularFirestoreCollectionGroup } from './collection-group/collection-group'; -import { - FIREBASE_APP_NAME, - FIREBASE_OPTIONS, - FirebaseAppConfig, - FirebaseOptions, - ÉĩAngularFireSchedulers, - ÉĩfirebaseAppFactory, - ÉĩkeepUnstableUntilFirstFactory -} from '@angular/fire'; -import { isPlatformServer } from '@angular/common'; -import { firestore } from 'firebase/app'; -import 'firebase/firestore'; -const atFirestore = require('@firebase/firestore'); -import firebase from 'firebase/app'; - -/** - * The value of this token determines whether or not the firestore will have persistance enabled - */ -export const ENABLE_PERSISTENCE = new InjectionToken('angularfire2.enableFirestorePersistence'); -export const PERSISTENCE_SETTINGS = new InjectionToken('angularfire2.firestore.persistenceSettings'); -export const SETTINGS = new InjectionToken('angularfire2.firestore.settings'); - -/** - * A utility methods for associating a collection reference with - * a query. - * - * @param collectionRef - A collection reference to query - * @param queryFn - The callback to create a query - * - * Example: - * const { query, ref } = associateQuery(docRef.collection('items'), ref => { - * return ref.where('age', '<', 200); - * }); - */ -export function associateQuery(collectionRef: CollectionReference, queryFn = ref => ref): AssociatedReference { - const query = queryFn(collectionRef); - const ref = collectionRef; - return { query, ref }; -} - -/** - * AngularFirestore Service - * - * This service is the main entry point for this feature module. It provides - * an API for creating Collection and Reference services. These services can - * then be used to do data updates and observable streams of the data. - * - * Example: - * - * import { Component } from '@angular/core'; - * import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from '@angular/fire/firestore'; - * import { Observable } from 'rxjs/Observable'; - * import { from } from 'rxjs/observable'; - * - * @Component({ - * selector: 'app-my-component', - * template: ` - *

Items for {{ (profile | async)?.name }} - *
    - *
  • {{ item.name }}
  • - *
- *
- * - * - *
- * ` - * }) - * export class MyComponent implements OnInit { - * - * // services for data operations and data streaming - * private readonly itemsRef: AngularFirestoreCollection; - * private readonly profileRef: AngularFirestoreDocument; - * - * // observables for template - * items: Observable; - * profile: Observable; - * - * // inject main service - * constructor(private readonly afs: AngularFirestore) {} - * - * ngOnInit() { - * this.itemsRef = afs.collection('items', ref => ref.where('user', '==', 'davideast').limit(10)); - * this.items = this.itemsRef.valueChanges().map(snap => snap.docs.map(data => doc.data())); - * // this.items = from(this.itemsRef); // you can also do this with no mapping - * - * this.profileRef = afs.doc('users/davideast'); - * this.profile = this.profileRef.valueChanges(); - * } - * - * addItem(name: string) { - * const user = 'davideast'; - * this.itemsRef.add({ name, user }); - * } - * } - */ -@Injectable({ - providedIn: 'any' -}) -export class AngularFirestore { - public readonly firestore: firestore.Firestore; - public readonly persistenceEnabled$: Observable; - public readonly schedulers: ÉĩAngularFireSchedulers; - public readonly keepUnstableUntilFirst: (obs: Observable) => Observable; - - /** - * Each Feature of AngularFire has a FirebaseApp injected. This way we - * don't rely on the main Firebase App instance and we can create named - * apps and use multiple apps. - */ - constructor( - @Inject(FIREBASE_OPTIONS) options: FirebaseOptions, - @Optional() @Inject(FIREBASE_APP_NAME) nameOrConfig: string | FirebaseAppConfig | null | undefined, - @Optional() @Inject(ENABLE_PERSISTENCE) shouldEnablePersistence: boolean | null, - @Optional() @Inject(SETTINGS) settings: Settings | null, - // tslint:disable-next-line:ban-types - @Inject(PLATFORM_ID) platformId: Object, - zone: NgZone, - @Optional() @Inject(PERSISTENCE_SETTINGS) persistenceSettings: PersistenceSettings | null - ) { - this.schedulers = new ÉĩAngularFireSchedulers(zone); - this.keepUnstableUntilFirst = ÉĩkeepUnstableUntilFirstFactory(this.schedulers); - - this.firestore = zone.runOutsideAngular(() => { - const app = ÉĩfirebaseAppFactory(options, zone, nameOrConfig); - // INVESTIGATE this seems to be required because in the browser build registerFirestore is an Object? - // seems like we're fighting ngcc. In the server firestore() isn't available, so I have to register - // in the browser registerFirestore is not a function... maybe this is an underlying firebase-js-sdk issue - if ('registerFirestore' in atFirestore) { - (atFirestore as any).registerFirestore(firebase as any); - } - const firestore = app.firestore(); - if (settings) { - firestore.settings(settings); - } - return firestore; - }); - - if (shouldEnablePersistence && !isPlatformServer(platformId)) { - // We need to try/catch here because not all enablePersistence() failures are caught - // https://github.com/firebase/firebase-js-sdk/issues/608 - const enablePersistence = () => { - try { - return from(this.firestore.enablePersistence(persistenceSettings || undefined).then(() => true, () => false)); - } catch (e) { - return of(false); - } - }; - this.persistenceEnabled$ = zone.runOutsideAngular(enablePersistence); - } else { - this.persistenceEnabled$ = of(false); - } - } - - /** - * Create a reference to a Firestore Collection based on a path or - * CollectionReference and an optional query function to narrow the result - * set. - */ - collection(path: string, queryFn?: QueryFn): AngularFirestoreCollection; - // tslint:disable-next-line:unified-signatures - collection(ref: CollectionReference, queryFn?: QueryFn): AngularFirestoreCollection; - collection(pathOrRef: string | CollectionReference, queryFn?: QueryFn): AngularFirestoreCollection { - let collectionRef: CollectionReference; - if (typeof pathOrRef === 'string') { - collectionRef = this.firestore.collection(pathOrRef); - } else { - collectionRef = pathOrRef; - } - const { ref, query } = associateQuery(collectionRef, queryFn); - const refInZone = this.schedulers.ngZone.run(() => ref); - return new AngularFirestoreCollection(refInZone, query, this); +import { ÉĩgetAllInstancesOf } from '@angular/fire'; +import { Firestore as FirebaseFirestore } from 'firebase/firestore'; +import { from, timer } from 'rxjs'; +import { concatMap, distinct } from 'rxjs/operators'; + +// see notes in core/firebase.app.module.ts for why we're building the class like this + +export interface Firestore extends FirebaseFirestore {} + +export class Firestore { + constructor(firestore: FirebaseFirestore) { + return firestore; } +} - /** - * Create a reference to a Firestore Collection Group based on a collectionId - * and an optional query function to narrow the result - * set. - */ - collectionGroup(collectionId: string, queryGroupFn?: QueryGroupFn): AngularFirestoreCollectionGroup { - const queryFn = queryGroupFn || (ref => ref); - const collectionGroup: Query = this.firestore.collectionGroup(collectionId); - return new AngularFirestoreCollectionGroup(queryFn(collectionGroup), this); - } +export const FIRESTORE_PROVIDER_NAME = 'firestore'; - /** - * Create a reference to a Firestore Document based on a path or - * DocumentReference. Note that documents are not queryable because they are - * simply objects. However, documents have sub-collections that return a - * Collection reference and can be queried. - */ - doc(path: string): AngularFirestoreDocument; - // tslint:disable-next-line:unified-signatures - doc(ref: DocumentReference): AngularFirestoreDocument; - doc(pathOrRef: string | DocumentReference): AngularFirestoreDocument { - let ref: DocumentReference; - if (typeof pathOrRef === 'string') { - ref = this.firestore.doc(pathOrRef); - } else { - ref = pathOrRef; - } - const refInZone = this.schedulers.ngZone.run(() => ref); - return new AngularFirestoreDocument(refInZone, this); - } + +export interface FirestoreInstances extends Array {} - /** - * Returns a generated Firestore Document Id. - */ - createId() { - return this.firestore.collection('_').doc().id; +export class FirestoreInstances { + constructor() { + return ÉĩgetAllInstancesOf(FIRESTORE_PROVIDER_NAME); } } + +export const firestoreInstance$ = timer(0, 300).pipe( + concatMap(() => from(ÉĩgetAllInstancesOf(FIRESTORE_PROVIDER_NAME))), + distinct(), +); diff --git a/src/firestore/interfaces.ts b/src/firestore/interfaces.ts deleted file mode 100644 index 8a8461fdd..000000000 --- a/src/firestore/interfaces.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Subscriber } from 'rxjs'; -import { firestore } from 'firebase/app'; - -export type Settings = firestore.Settings; -export type CollectionReference = firestore.CollectionReference; -export type DocumentReference = firestore.DocumentReference; -export type PersistenceSettings = firestore.PersistenceSettings; -export type DocumentChangeType = firestore.DocumentChangeType; -export type SnapshotOptions = firestore.SnapshotOptions; -export type FieldPath = firestore.FieldPath; -export type Query = firestore.Query; - -export type SetOptions = firestore.SetOptions; -export type DocumentData = firestore.DocumentData; - -export interface DocumentSnapshotExists extends firestore.DocumentSnapshot { - readonly exists: true; - data(options?: SnapshotOptions): T; -} - -export interface DocumentSnapshotDoesNotExist extends firestore.DocumentSnapshot { - readonly exists: false; - data(options?: SnapshotOptions): undefined; - get(fieldPath: string | FieldPath, options?: SnapshotOptions): undefined; -} - -export type DocumentSnapshot = DocumentSnapshotExists | DocumentSnapshotDoesNotExist; - -export interface QueryDocumentSnapshot extends firestore.QueryDocumentSnapshot { - data(options?: SnapshotOptions): T; -} - -export interface QuerySnapshot extends firestore.QuerySnapshot { - readonly docs: QueryDocumentSnapshot[]; -} - -export interface DocumentChange extends firestore.DocumentChange { - readonly doc: QueryDocumentSnapshot; -} - -export interface DocumentChangeAction { - type: DocumentChangeType; - payload: DocumentChange; -} - -export interface Action { - type: string; - payload: T; -} - -export interface Reference { - onSnapshot: (sub: Subscriber) => any; -} - -// A convience type for making a query. -// Example: const query = (ref) => ref.where('name', == 'david'); -export type QueryFn = (ref: CollectionReference) => Query; - -export type QueryGroupFn = (query: Query) => Query; - -/** - * A structure that provides an association between a reference - * and a query on that reference. Note: Performing operations - * on the reference can lead to confusing results with complicated - * queries. - * - * Example: - * - * const query = ref.where('type', '==', 'Book'). - * .where('price', '>' 18.00) - * .where('price', '<' 100.00) - * .where('category', '==', 'Fiction') - * .where('publisher', '==', 'BigPublisher') - * - * // This addition would not be a result of the query above - * ref.add({ - * type: 'Magazine', - * price: 4.99, - * category: 'Sports', - * publisher: 'SportsPublisher' - * }); - */ -export interface AssociatedReference { - ref: CollectionReference; - query: Query; -} diff --git a/src/firestore/lite/firebase.ts b/src/firestore/lite/firebase.ts new file mode 100644 index 000000000..47ef25367 --- /dev/null +++ b/src/firestore/lite/firebase.ts @@ -0,0 +1,84 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +export * from 'firebase/firestore/lite'; +import { ÉĩzoneWrap } from '@angular/fire'; +import { + addDoc as _addDoc, + aggregateFieldEqual as _aggregateFieldEqual, + aggregateQuerySnapshotEqual as _aggregateQuerySnapshotEqual, + and as _and, + collection as _collection, + collectionGroup as _collectionGroup, + connectFirestoreEmulator as _connectFirestoreEmulator, + deleteDoc as _deleteDoc, + deleteField as _deleteField, + doc as _doc, + documentId as _documentId, + endAt as _endAt, + endBefore as _endBefore, + getAggregate as _getAggregate, + getCount as _getCount, + getDoc as _getDoc, + getDocs as _getDocs, + getFirestore as _getFirestore, + increment as _increment, + initializeFirestore as _initializeFirestore, + limit as _limit, + limitToLast as _limitToLast, + or as _or, + orderBy as _orderBy, + query as _query, + queryEqual as _queryEqual, + refEqual as _refEqual, + runTransaction as _runTransaction, + setDoc as _setDoc, + setLogLevel as _setLogLevel, + snapshotEqual as _snapshotEqual, + startAfter as _startAfter, + startAt as _startAt, + sum as _sum, + terminate as _terminate, + updateDoc as _updateDoc, + vector as _vector, + where as _where, + writeBatch as _writeBatch +} from 'firebase/firestore/lite'; + +export const addDoc = ÉĩzoneWrap(_addDoc, true, 2); +export const aggregateFieldEqual = ÉĩzoneWrap(_aggregateFieldEqual, true, 2); +export const aggregateQuerySnapshotEqual = ÉĩzoneWrap(_aggregateQuerySnapshotEqual, true, 2); +export const and = ÉĩzoneWrap(_and, true, 2); +export const collection = ÉĩzoneWrap(_collection, true, 2); +export const collectionGroup = ÉĩzoneWrap(_collectionGroup, true, 2); +export const connectFirestoreEmulator = ÉĩzoneWrap(_connectFirestoreEmulator, true); +export const deleteDoc = ÉĩzoneWrap(_deleteDoc, true, 2); +export const deleteField = ÉĩzoneWrap(_deleteField, true, 2); +export const doc = ÉĩzoneWrap(_doc, true, 2); +export const documentId = ÉĩzoneWrap(_documentId, true, 2); +export const endAt = ÉĩzoneWrap(_endAt, true, 2); +export const endBefore = ÉĩzoneWrap(_endBefore, true, 2); +export const getAggregate = ÉĩzoneWrap(_getAggregate, true); +export const getCount = ÉĩzoneWrap(_getCount, true); +export const getDoc = ÉĩzoneWrap(_getDoc, true); +export const getDocs = ÉĩzoneWrap(_getDocs, true); +export const getFirestore = ÉĩzoneWrap(_getFirestore, true); +export const increment = ÉĩzoneWrap(_increment, true, 2); +export const initializeFirestore = ÉĩzoneWrap(_initializeFirestore, true); +export const limit = ÉĩzoneWrap(_limit, true, 2); +export const limitToLast = ÉĩzoneWrap(_limitToLast, true, 2); +export const or = ÉĩzoneWrap(_or, true, 2); +export const orderBy = ÉĩzoneWrap(_orderBy, true, 2); +export const query = ÉĩzoneWrap(_query, true, 2); +export const queryEqual = ÉĩzoneWrap(_queryEqual, true, 2); +export const refEqual = ÉĩzoneWrap(_refEqual, true, 2); +export const runTransaction = ÉĩzoneWrap(_runTransaction, true); +export const setDoc = ÉĩzoneWrap(_setDoc, true, 2); +export const setLogLevel = ÉĩzoneWrap(_setLogLevel, true); +export const snapshotEqual = ÉĩzoneWrap(_snapshotEqual, true, 2); +export const startAfter = ÉĩzoneWrap(_startAfter, true, 2); +export const startAt = ÉĩzoneWrap(_startAt, true, 2); +export const sum = ÉĩzoneWrap(_sum, true, 2); +export const terminate = ÉĩzoneWrap(_terminate, true); +export const updateDoc = ÉĩzoneWrap(_updateDoc, true, 2); +export const vector = ÉĩzoneWrap(_vector, true, 2); +export const where = ÉĩzoneWrap(_where, true, 2); +export const writeBatch = ÉĩzoneWrap(_writeBatch, true, 2); diff --git a/src/firestore/lite/lite.module.ts b/src/firestore/lite/lite.module.ts new file mode 100644 index 000000000..f72409b93 --- /dev/null +++ b/src/firestore/lite/lite.module.ts @@ -0,0 +1,82 @@ +import { + EnvironmentProviders, + InjectionToken, + Injector, + NgModule, + NgZone, + Optional, + makeEnvironmentProviders, +} from '@angular/core'; +import { VERSION, ÉĩAngularFireSchedulers, ÉĩgetDefaultInstanceOf } from '@angular/fire'; +import { FirebaseApp, FirebaseApps } from '@angular/fire/app'; +import { AppCheckInstances} from "@angular/fire/app-check"; +import { AuthInstances } from '@angular/fire/auth'; +import { registerVersion } from 'firebase/app'; +import { Firestore as FirebaseFirestore } from 'firebase/firestore/lite'; +import { FIRESTORE_PROVIDER_NAME, Firestore, FirestoreInstances } from './lite'; + +export const PROVIDED_FIRESTORE_INSTANCES = new InjectionToken('angularfire2.firestore-lite-instances'); + +export function defaultFirestoreInstanceFactory(provided: FirebaseFirestore[]|undefined, defaultApp: FirebaseApp) { + const defaultFirestore = ÉĩgetDefaultInstanceOf(FIRESTORE_PROVIDER_NAME, provided, defaultApp); + return defaultFirestore && new Firestore(defaultFirestore); +} + +export function firestoreInstanceFactory(fn: (injector: Injector) => FirebaseFirestore) { + return (zone: NgZone, injector: Injector) => { + const firestore = zone.runOutsideAngular(() => fn(injector)); + return new Firestore(firestore); + }; +} + +const FIRESTORE_INSTANCES_PROVIDER = { + provide: FirestoreInstances, + deps: [ + [new Optional(), PROVIDED_FIRESTORE_INSTANCES ], + ] +}; + +const DEFAULT_FIRESTORE_INSTANCE_PROVIDER = { + provide: Firestore, + useFactory: defaultFirestoreInstanceFactory, + deps: [ + [new Optional(), PROVIDED_FIRESTORE_INSTANCES ], + FirebaseApp, + ] +}; + +@NgModule({ + providers: [ + DEFAULT_FIRESTORE_INSTANCE_PROVIDER, + FIRESTORE_INSTANCES_PROVIDER, + ] +}) +export class FirestoreModule { + constructor() { + registerVersion('angularfire', VERSION.full, 'lite'); + } +} + +export function provideFirestore(fn: (injector: Injector) => FirebaseFirestore, ...deps: any[]): EnvironmentProviders { + registerVersion('angularfire', VERSION.full, 'lite'); + + return makeEnvironmentProviders([ + DEFAULT_FIRESTORE_INSTANCE_PROVIDER, + FIRESTORE_INSTANCES_PROVIDER, + { + provide: PROVIDED_FIRESTORE_INSTANCES, + useFactory: firestoreInstanceFactory(fn), + multi: true, + deps: [ + NgZone, + Injector, + ÉĩAngularFireSchedulers, + FirebaseApps, + // Firestore+Auth work better if Auth is loaded first + [new Optional(), AuthInstances ], + [new Optional(), AppCheckInstances ], + ...deps, + ] + } + ]); +} diff --git a/src/firestore/lite/lite.spec.ts b/src/firestore/lite/lite.spec.ts new file mode 100644 index 000000000..83cbb54f7 --- /dev/null +++ b/src/firestore/lite/lite.spec.ts @@ -0,0 +1,39 @@ +import { TestBed } from '@angular/core/testing'; +import { FirebaseApp, getApp, initializeApp, provideFirebaseApp } from '@angular/fire/app'; +import { Firestore, connectFirestoreEmulator, getFirestore, provideFirestore } from '@angular/fire/firestore/lite'; +import { COMMON_CONFIG, firestoreEmulatorPort } from '../../test-config'; +import { rando } from '../../utils'; + +describe('Firestore-lite', () => { + let app: FirebaseApp; + let firestore: Firestore; + let providedFirestore: Firestore; + let appName: string; + + describe('single injection', () => { + + beforeEach(() => { + appName = rando(); + TestBed.configureTestingModule({ + providers: [ + provideFirebaseApp(() => initializeApp(COMMON_CONFIG, appName)), + provideFirestore(() => { + providedFirestore = getFirestore(getApp(appName)); + connectFirestoreEmulator(providedFirestore, 'localhost', firestoreEmulatorPort); + return providedFirestore; + }), + ], + }); + app = TestBed.inject(FirebaseApp); + firestore = TestBed.inject(Firestore); + }); + + it('should be injectable', () => { + expect(providedFirestore).toBeTruthy(); + expect(firestore).toEqual(providedFirestore); + expect(firestore.app).toEqual(app); + }); + + }); + +}); diff --git a/src/firestore/lite/lite.ts b/src/firestore/lite/lite.ts new file mode 100644 index 000000000..a97c559cb --- /dev/null +++ b/src/firestore/lite/lite.ts @@ -0,0 +1,30 @@ +import { ÉĩgetAllInstancesOf } from '@angular/fire'; +import { Firestore as FirebaseFirestore } from 'firebase/firestore/lite'; +import { from, timer } from 'rxjs'; +import { concatMap, distinct } from 'rxjs/operators'; + +// see notes in core/firebase.app.module.ts for why we're building the class like this + +export interface Firestore extends FirebaseFirestore {} + +export class Firestore { + constructor(firestore: FirebaseFirestore) { + return firestore; + } +} + +export const FIRESTORE_PROVIDER_NAME = 'firestore/lite'; + + +export interface FirestoreInstances extends Array {} + +export class FirestoreInstances { + constructor() { + return ÉĩgetAllInstancesOf(FIRESTORE_PROVIDER_NAME); + } +} + +export const firestoreInstance$ = timer(0, 300).pipe( + concatMap(() => from(ÉĩgetAllInstancesOf(FIRESTORE_PROVIDER_NAME))), + distinct(), +); diff --git a/src/firestore/lite/ng-package.json b/src/firestore/lite/ng-package.json new file mode 100644 index 000000000..d3032da35 --- /dev/null +++ b/src/firestore/lite/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/firestore/lite/package.json b/src/firestore/lite/package.json new file mode 100644 index 000000000..5eaf9d5f0 --- /dev/null +++ b/src/firestore/lite/package.json @@ -0,0 +1,3 @@ +{ + "$schema": "../../../node_modules/ng-packagr/package.schema.json" +} diff --git a/src/firestore/lite/public_api.ts b/src/firestore/lite/public_api.ts new file mode 100644 index 000000000..6c4ad149f --- /dev/null +++ b/src/firestore/lite/public_api.ts @@ -0,0 +1,4 @@ +export { Firestore, FirestoreInstances, firestoreInstance$ } from './lite'; +export { FirestoreModule, provideFirestore } from './lite.module'; +export * from './rxfire'; +export * from './firebase'; diff --git a/src/firestore/lite/rxfire.ts b/src/firestore/lite/rxfire.ts new file mode 100644 index 000000000..8e1d5e2b5 --- /dev/null +++ b/src/firestore/lite/rxfire.ts @@ -0,0 +1,21 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +import { ÉĩzoneWrap } from '@angular/fire'; +import { + collection as _collection, + collectionCount as _collectionCount, + collectionCountSnap as _collectionCountSnap, + collectionData as _collectionData, + doc as _doc, + docData as _docData, + fromRef as _fromRef, + snapToData as _snapToData +} from 'rxfire/firestore/lite'; + +export const collectionSnapshots = ÉĩzoneWrap(_collection, true); +export const collectionCount = ÉĩzoneWrap(_collectionCount, true); +export const collectionCountSnap = ÉĩzoneWrap(_collectionCountSnap, true); +export const collectionData = ÉĩzoneWrap(_collectionData, true); +export const docSnapshots = ÉĩzoneWrap(_doc, true); +export const docData = ÉĩzoneWrap(_docData, true); +export const fromRef = ÉĩzoneWrap(_fromRef, true); +export const snapToData = ÉĩzoneWrap(_snapToData, true); diff --git a/src/firestore/ng-package.json b/src/firestore/ng-package.json new file mode 100644 index 000000000..d3032da35 --- /dev/null +++ b/src/firestore/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/firestore/observable/fromRef.ts b/src/firestore/observable/fromRef.ts deleted file mode 100644 index bfdd345e1..000000000 --- a/src/firestore/observable/fromRef.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { asyncScheduler, Observable, SchedulerLike } from 'rxjs'; -import { Action, DocumentReference, DocumentSnapshot, Query, QuerySnapshot, Reference } from '../interfaces'; -import { map } from 'rxjs/operators'; - -function _fromRef(ref: Reference, scheduler: SchedulerLike = asyncScheduler): Observable { - return new Observable(subscriber => { - let unsubscribe; - if (scheduler != null) { - scheduler.schedule(() => { - unsubscribe = ref.onSnapshot(subscriber); - }); - } else { - unsubscribe = ref.onSnapshot(subscriber); - } - - return () => { - if (unsubscribe != null) { - unsubscribe(); - } - }; - }); -} - -export function fromRef(ref: DocumentReference | Query, scheduler?: SchedulerLike) { - return _fromRef(ref, scheduler); -} - -export function fromDocRef(ref: DocumentReference, scheduler?: SchedulerLike): Observable>> { - return fromRef>(ref, scheduler) - .pipe( - map(payload => ({ payload, type: 'value' })) - ); -} - -export function fromCollectionRef(ref: Query, scheduler?: SchedulerLike): Observable>> { - return fromRef>(ref, scheduler).pipe(map(payload => ({ payload, type: 'query' }))); -} diff --git a/src/firestore/package.json b/src/firestore/package.json index 75b34a51b..8dce418a5 100644 --- a/src/firestore/package.json +++ b/src/firestore/package.json @@ -1,12 +1,3 @@ { - "$schema": "../../node_modules/ng-packagr/package.schema.json", - "ngPackage": { - "lib": { - "entryFile": "public_api.ts", - "umdModuleIds": { - "firebase/app": "firebase", - "@firebase/firestore": "firebase-firestore" - } - } - } + "$schema": "../../node_modules/ng-packagr/package.schema.json" } diff --git a/src/firestore/public_api.ts b/src/firestore/public_api.ts index 1670f1451..7eb680749 100644 --- a/src/firestore/public_api.ts +++ b/src/firestore/public_api.ts @@ -1,8 +1,4 @@ -export * from './firestore'; -export * from './firestore.module'; -export * from './collection/collection'; -export * from './collection-group/collection-group'; -export * from './document/document'; -export * from './collection/changes'; -export * from './observable/fromRef'; -export * from './interfaces'; +export { Firestore, FirestoreInstances, firestoreInstance$ } from './firestore'; +export { provideFirestore, FirestoreModule } from './firestore.module'; +export * from './rxfire'; +export * from './firebase'; diff --git a/src/firestore/rxfire.ts b/src/firestore/rxfire.ts new file mode 100644 index 000000000..6b2eb8e92 --- /dev/null +++ b/src/firestore/rxfire.ts @@ -0,0 +1,27 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +import { ÉĩzoneWrap } from '@angular/fire'; +import { + auditTrail as _auditTrail, + collection as _collection, + collectionChanges as _collectionChanges, + collectionCount as _collectionCount, + collectionCountSnap as _collectionCountSnap, + collectionData as _collectionData, + doc as _doc, + docData as _docData, + fromRef as _fromRef, + snapToData as _snapToData, + sortedChanges as _sortedChanges +} from 'rxfire/firestore'; + +export const auditTrail = ÉĩzoneWrap(_auditTrail, true); +export const collectionSnapshots = ÉĩzoneWrap(_collection, true); +export const collectionChanges = ÉĩzoneWrap(_collectionChanges, true); +export const collectionCount = ÉĩzoneWrap(_collectionCount, true); +export const collectionCountSnap = ÉĩzoneWrap(_collectionCountSnap, true); +export const collectionData = ÉĩzoneWrap(_collectionData, true); +export const docSnapshots = ÉĩzoneWrap(_doc, true); +export const docData = ÉĩzoneWrap(_docData, true); +export const fromRef = ÉĩzoneWrap(_fromRef, true); +export const snapToData = ÉĩzoneWrap(_snapToData, true); +export const sortedChanges = ÉĩzoneWrap(_sortedChanges, true); diff --git a/src/firestore/utils.spec.ts b/src/firestore/utils.spec.ts deleted file mode 100644 index e0bfa187d..000000000 --- a/src/firestore/utils.spec.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { firestore } from 'firebase/app'; -import { AngularFirestoreCollection } from './collection/collection'; - -export interface Stock { - name: string; - price: number; -} - -export const FAKE_STOCK_DATA = { name: 'FAKE', price: 1 }; - -export const randomName = (firestore): string => firestore.collection('a').doc().id; - -export const createRandomStocks = async (firestore: firestore.Firestore, collectionRef: firestore.CollectionReference, numberOfItems) => { - // Create a batch to update everything at once - const batch = firestore.batch(); - // Store the random names to delete them later - const count = 0; - let names: string[] = []; - Array.from(Array(numberOfItems)).forEach((a, i) => { - const name = randomName(firestore); - batch.set(collectionRef.doc(name), FAKE_STOCK_DATA); - names = [...names, name]; - }); - // Create the batch entries - // Commit! - await batch.commit(); - return names; -}; - -export function deleteThemAll(names, ref) { - const promises = names.map(name => ref.doc(name).delete()); - return Promise.all(promises); -} - -export function delayUpdate(collection: AngularFirestoreCollection|firestore.CollectionReference, path, data, delay = 250) { - setTimeout(() => { - collection.doc(path).update(data); - }, delay); -} - -export function delayAdd(collection: AngularFirestoreCollection|firestore.CollectionReference, path, data, delay = 250) { - setTimeout(() => { - collection.doc(path).set(data); - }, delay); -} - -export function delayDelete(collection: AngularFirestoreCollection|firestore.CollectionReference, path, delay = 250) { - setTimeout(() => { - collection.doc(path).delete(); - }, delay); -} - -export const rando = () => (Math.random() + 1).toString(36).substring(7); diff --git a/src/functions/firebase.ts b/src/functions/firebase.ts new file mode 100644 index 000000000..204f60574 --- /dev/null +++ b/src/functions/firebase.ts @@ -0,0 +1,14 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +export * from 'firebase/functions'; +import { ÉĩzoneWrap } from '@angular/fire'; +import { + connectFunctionsEmulator as _connectFunctionsEmulator, + getFunctions as _getFunctions, + httpsCallable as _httpsCallable, + httpsCallableFromURL as _httpsCallableFromURL +} from 'firebase/functions'; + +export const connectFunctionsEmulator = ÉĩzoneWrap(_connectFunctionsEmulator, true); +export const getFunctions = ÉĩzoneWrap(_getFunctions, true); +export const httpsCallable = ÉĩzoneWrap(_httpsCallable, true); +export const httpsCallableFromURL = ÉĩzoneWrap(_httpsCallableFromURL, true); diff --git a/src/functions/functions.module.ts b/src/functions/functions.module.ts index 94043b346..90e0c4331 100644 --- a/src/functions/functions.module.ts +++ b/src/functions/functions.module.ts @@ -1,7 +1,82 @@ -import { NgModule } from '@angular/core'; -import { AngularFireFunctions } from './functions'; +import { + EnvironmentProviders, + InjectionToken, + Injector, + NgModule, + NgZone, + Optional, + makeEnvironmentProviders, +} from '@angular/core'; +import { VERSION, ÉĩAngularFireSchedulers, ÉĩgetDefaultInstanceOf } from '@angular/fire'; +import { FirebaseApp, FirebaseApps } from '@angular/fire/app'; +import { AppCheckInstances } from '@angular/fire/app-check'; +import { AuthInstances } from '@angular/fire/auth'; +import { registerVersion } from 'firebase/app'; +import { Functions as FirebaseFunctions } from 'firebase/functions'; +import { FUNCTIONS_PROVIDER_NAME, Functions, FunctionsInstances } from './functions'; + +export const PROVIDED_FUNCTIONS_INSTANCES = new InjectionToken('angularfire2.functions-instances'); + +export function defaultFunctionsInstanceFactory(provided: FirebaseFunctions[]|undefined, defaultApp: FirebaseApp) { + const defaultAuth = ÉĩgetDefaultInstanceOf(FUNCTIONS_PROVIDER_NAME, provided, defaultApp); + return defaultAuth && new Functions(defaultAuth); +} + +export function functionsInstanceFactory(fn: (injector: Injector) => FirebaseFunctions) { + return (zone: NgZone, injector: Injector) => { + const functions = zone.runOutsideAngular(() => fn(injector)); + return new Functions(functions); + }; +} + +const FUNCTIONS_INSTANCES_PROVIDER = { + provide: FunctionsInstances, + deps: [ + [new Optional(), PROVIDED_FUNCTIONS_INSTANCES ], + ] +}; + +const DEFAULT_FUNCTIONS_INSTANCE_PROVIDER = { + provide: Functions, + useFactory: defaultFunctionsInstanceFactory, + deps: [ + [new Optional(), PROVIDED_FUNCTIONS_INSTANCES ], + FirebaseApp, + ] +}; @NgModule({ - providers: [ AngularFireFunctions ] + providers: [ + DEFAULT_FUNCTIONS_INSTANCE_PROVIDER, + FUNCTIONS_INSTANCES_PROVIDER, + ] }) -export class AngularFireFunctionsModule { } +export class FunctionsModule { + constructor() { + registerVersion('angularfire', VERSION.full, 'fn'); + } +} + +export function provideFunctions(fn: (injector: Injector) => FirebaseFunctions, ...deps: any[]): EnvironmentProviders { + registerVersion('angularfire', VERSION.full, 'fn'); + + return makeEnvironmentProviders([ + DEFAULT_FUNCTIONS_INSTANCE_PROVIDER, + FUNCTIONS_INSTANCES_PROVIDER, + { + provide: PROVIDED_FUNCTIONS_INSTANCES, + useFactory: functionsInstanceFactory(fn), + multi: true, + deps: [ + NgZone, + Injector, + ÉĩAngularFireSchedulers, + FirebaseApps, + // Defensively load Auth first, if provided + [new Optional(), AuthInstances ], + [new Optional(), AppCheckInstances ], + ...deps, + ] + } + ]); +} diff --git a/src/functions/functions.spec.ts b/src/functions/functions.spec.ts index f316f6ddf..8a06d2c6c 100644 --- a/src/functions/functions.spec.ts +++ b/src/functions/functions.spec.ts @@ -1,76 +1,37 @@ import { TestBed } from '@angular/core/testing'; -import { AngularFireModule, FIREBASE_APP_NAME, FIREBASE_OPTIONS, FirebaseApp } from '@angular/fire'; -import { AngularFireFunctions, AngularFireFunctionsModule, ORIGIN, REGION } from './public_api'; -import { COMMON_CONFIG } from '../test-config'; -import 'firebase/functions'; -import { rando } from '../firestore/utils.spec'; +import { FirebaseApp, getApp, initializeApp, provideFirebaseApp } from '@angular/fire/app'; +import { Functions, connectFunctionsEmulator, getFunctions, provideFunctions } from '@angular/fire/functions'; +import { COMMON_CONFIG, functionsEmulatorPort } from '../test-config'; +import { rando } from '../utils'; -describe('AngularFireFunctions', () => { +describe('Functions', () => { let app: FirebaseApp; - let afFns: AngularFireFunctions; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - AngularFireModule.initializeApp(COMMON_CONFIG, rando()), - AngularFireFunctionsModule - ] - }); - - app = TestBed.inject(FirebaseApp); - afFns = TestBed.inject(AngularFireFunctions); - }); - - afterEach(() => { - app.delete(); - }); - - it('should exist', () => { - expect(afFns instanceof AngularFireFunctions).toBe(true); - }); - - it('should have the Firebase Functions instance', () => { - expect(afFns.useFunctionsEmulator).toBeDefined(); - }); - -}); - -describe('AngularFireFunctions with different app', () => { - let app: FirebaseApp; - let afFns: AngularFireFunctions; - let firebaseAppName: string; - - beforeEach(() => { - firebaseAppName = rando(); - TestBed.configureTestingModule({ - imports: [ - AngularFireModule.initializeApp(COMMON_CONFIG, rando()), - AngularFireFunctionsModule - ], - providers: [ - { provide: FIREBASE_APP_NAME, useValue: firebaseAppName }, - { provide: FIREBASE_OPTIONS, useValue: COMMON_CONFIG }, - { provide: ORIGIN, useValue: 'http://0.0.0.0:9999' }, - { provide: REGION, useValue: 'asia-northeast1' } - ] - }); - - app = TestBed.inject(FirebaseApp); - afFns = TestBed.inject(AngularFireFunctions); - }); - - afterEach(() => { - app.delete(); - }); - - describe('', () => { - - it('should be an AngularFireAuth type', () => { - expect(afFns instanceof AngularFireFunctions).toEqual(true); + let functions: Functions; + let providedFunctions: Functions; + let appName: string; + + describe('single injection', () => { + + beforeEach(() => { + appName = rando(); + TestBed.configureTestingModule({ + providers: [ + provideFirebaseApp(() => initializeApp(COMMON_CONFIG, appName)), + provideFunctions(() => { + providedFunctions = getFunctions(getApp(appName)); + connectFunctionsEmulator(providedFunctions, 'localhost', functionsEmulatorPort); + return providedFunctions; + }), + ], + }); + app = TestBed.inject(FirebaseApp); + functions = TestBed.inject(Functions); }); - it('should have the Firebase Functions instance', () => { - expect(afFns.useFunctionsEmulator).toBeDefined(); + it('should be injectable', () => { + expect(providedFunctions).toBeTruthy(); + expect(functions).toEqual(providedFunctions); + expect(functions.app).toEqual(app); }); }); diff --git a/src/functions/functions.ts b/src/functions/functions.ts index 8a6da175e..f378d4f99 100644 --- a/src/functions/functions.ts +++ b/src/functions/functions.ts @@ -1,64 +1,30 @@ -import { Inject, Injectable, InjectionToken, NgZone, Optional } from '@angular/core'; -import { from, Observable, of } from 'rxjs'; -import { map, observeOn, shareReplay, switchMap, tap } from 'rxjs/operators'; -import { - FIREBASE_APP_NAME, - FIREBASE_OPTIONS, - FirebaseAppConfig, - FirebaseOptions, - ÉĩAngularFireSchedulers, - ÉĩfirebaseAppFactory, - ÉĩlazySDKProxy, - ÉĩPromiseProxy -} from '@angular/fire'; -import { functions } from 'firebase/app'; - -export const ORIGIN = new InjectionToken('angularfire2.functions.origin'); -export const REGION = new InjectionToken('angularfire2.functions.region'); - -// override httpsCallable for compatibility with 5.x -export interface AngularFireFunctions extends Omit<ÉĩPromiseProxy, 'httpsCallable'> { +import { ÉĩgetAllInstancesOf } from '@angular/fire'; +import { Functions as FirebaseFunctions } from 'firebase/functions'; +import { from, timer } from 'rxjs'; +import { concatMap, distinct } from 'rxjs/operators'; + +// see notes in core/firebase.app.module.ts for why we're building the class like this + +export interface Functions extends FirebaseFunctions {} + +export class Functions { + constructor(functions: FirebaseFunctions) { + return functions; + } } -@Injectable({ - providedIn: 'any' -}) -export class AngularFireFunctions { - - public readonly httpsCallable: (name: string) => (data: T) => Observable; - - constructor( - @Inject(FIREBASE_OPTIONS) options: FirebaseOptions, - @Optional() @Inject(FIREBASE_APP_NAME) nameOrConfig: string | FirebaseAppConfig | null | undefined, - zone: NgZone, - @Optional() @Inject(REGION) region: string | null, - @Optional() @Inject(ORIGIN) origin: string | null - ) { - const schedulers = new ÉĩAngularFireSchedulers(zone); +export const FUNCTIONS_PROVIDER_NAME = 'functions'; - const functions = of(undefined).pipe( - observeOn(schedulers.outsideAngular), - switchMap(() => import('firebase/functions')), - tap((it: any) => it), - map(() => ÉĩfirebaseAppFactory(options, zone, nameOrConfig)), - map(app => app.functions(region || undefined)), - tap(functions => { - if (origin) { - functions.useFunctionsEmulator(origin); - } - }), - shareReplay({ bufferSize: 1, refCount: false }) - ); - - this.httpsCallable = (name: string) => - (data: T) => from(functions).pipe( - observeOn(schedulers.insideAngular), - switchMap(functions => functions.httpsCallable(name)(data)), - map(r => r.data as R) - ); - - return ÉĩlazySDKProxy(this, functions, zone); + +export interface FunctionsInstances extends Array {} +export class FunctionsInstances { + constructor() { + return ÉĩgetAllInstancesOf(FUNCTIONS_PROVIDER_NAME); } - } + +export const functionInstance$ = timer(0, 300).pipe( + concatMap(() => from(ÉĩgetAllInstancesOf(FUNCTIONS_PROVIDER_NAME))), + distinct(), +); diff --git a/src/functions/ng-package.json b/src/functions/ng-package.json new file mode 100644 index 000000000..d3032da35 --- /dev/null +++ b/src/functions/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/functions/package.json b/src/functions/package.json index a559c462f..8dce418a5 100644 --- a/src/functions/package.json +++ b/src/functions/package.json @@ -1,11 +1,3 @@ { - "$schema": "../../node_modules/ng-packagr/package.schema.json", - "ngPackage": { - "lib": { - "entryFile": "public_api.ts", - "umdModuleIds": { - "firebase/app": "firebase" - } - } - } + "$schema": "../../node_modules/ng-packagr/package.schema.json" } diff --git a/src/functions/public_api.ts b/src/functions/public_api.ts index ebea7ec53..0ae0209af 100644 --- a/src/functions/public_api.ts +++ b/src/functions/public_api.ts @@ -1,2 +1,4 @@ -export * from './functions'; -export * from './functions.module'; +export { provideFunctions, FunctionsModule } from './functions.module'; +export { Functions, FunctionsInstances, functionInstance$ } from './functions'; +export * from './rxfire'; +export * from './firebase'; diff --git a/src/functions/rxfire.ts b/src/functions/rxfire.ts new file mode 100644 index 000000000..3c6c2f7bd --- /dev/null +++ b/src/functions/rxfire.ts @@ -0,0 +1,7 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +import { ÉĩzoneWrap } from '@angular/fire'; +import { + httpsCallable as _httpsCallable +} from 'rxfire/functions'; + +export const httpsCallableData = ÉĩzoneWrap(_httpsCallable, true); diff --git a/src/messaging/firebase.ts b/src/messaging/firebase.ts new file mode 100644 index 000000000..69e76baca --- /dev/null +++ b/src/messaging/firebase.ts @@ -0,0 +1,16 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +export * from 'firebase/messaging'; +import { ÉĩzoneWrap } from '@angular/fire'; +import { + deleteToken as _deleteToken, + getMessaging as _getMessaging, + getToken as _getToken, + isSupported as _isSupported, + onMessage as _onMessage +} from 'firebase/messaging'; + +export const deleteToken = ÉĩzoneWrap(_deleteToken, true, 2); +export const getMessaging = ÉĩzoneWrap(_getMessaging, true); +export const getToken = ÉĩzoneWrap(_getToken, true); +export const isSupported = ÉĩzoneWrap(_isSupported, false); +export const onMessage = ÉĩzoneWrap(_onMessage, false); diff --git a/src/messaging/messaging.module.ts b/src/messaging/messaging.module.ts index e561c8d49..0942e053a 100644 --- a/src/messaging/messaging.module.ts +++ b/src/messaging/messaging.module.ts @@ -1,7 +1,83 @@ -import { NgModule } from '@angular/core'; -import { AngularFireMessaging } from './messaging'; +import { isPlatformServer } from '@angular/common'; +import { + EnvironmentProviders, + InjectionToken, + Injector, + NgModule, + NgZone, + Optional, + PLATFORM_ID, + makeEnvironmentProviders, +} from '@angular/core'; +import { VERSION, ÉĩAngularFireSchedulers, ÉĩgetDefaultInstanceOf } from '@angular/fire'; +import { FirebaseApp, FirebaseApps } from '@angular/fire/app'; +import { registerVersion } from 'firebase/app'; +import { Messaging as FirebaseMessaging } from 'firebase/messaging'; +import { MESSAGING_PROVIDER_NAME, Messaging, MessagingInstances } from './messaging'; + +const PROVIDED_MESSAGING_INSTANCES = new InjectionToken('angularfire2.messaging-instances'); + +export function defaultMessagingInstanceFactory(provided: FirebaseMessaging[]|undefined, defaultApp: FirebaseApp, platformId: object) { + if (isPlatformServer(platformId)) { return null; } + const defaultMessaging = ÉĩgetDefaultInstanceOf(MESSAGING_PROVIDER_NAME, provided, defaultApp); + return defaultMessaging && new Messaging(defaultMessaging); +} + +export function messagingInstanceFactory(fn: (injector: Injector) => FirebaseMessaging) { + return (zone: NgZone, injector: Injector, platformId: object) => { + if (isPlatformServer(platformId)) { return null; } + const messaging = zone.runOutsideAngular(() => fn(injector)); + return new Messaging(messaging); + }; +} + +const MESSAGING_INSTANCES_PROVIDER = { + provide: MessagingInstances, + deps: [ + [new Optional(), PROVIDED_MESSAGING_INSTANCES ], + ] +}; + +const DEFAULT_MESSAGING_INSTANCE_PROVIDER = { + provide: Messaging, + useFactory: defaultMessagingInstanceFactory, + deps: [ + [new Optional(), PROVIDED_MESSAGING_INSTANCES ], + FirebaseApp, + PLATFORM_ID, + ] +}; @NgModule({ - providers: [ AngularFireMessaging ] + providers: [ + DEFAULT_MESSAGING_INSTANCE_PROVIDER, + MESSAGING_INSTANCES_PROVIDER, + ] }) -export class AngularFireMessagingModule { } +export class MessagingModule { + constructor() { + registerVersion('angularfire', VERSION.full, 'fcm'); + } +} + +export function provideMessaging(fn: (injector: Injector) => FirebaseMessaging, ...deps: any[]): EnvironmentProviders { + registerVersion('angularfire', VERSION.full, 'fcm'); + + return makeEnvironmentProviders([ + DEFAULT_MESSAGING_INSTANCE_PROVIDER, + MESSAGING_INSTANCES_PROVIDER, + { + provide: PROVIDED_MESSAGING_INSTANCES, + useFactory: messagingInstanceFactory(fn), + multi: true, + deps: [ + NgZone, + Injector, + PLATFORM_ID, + ÉĩAngularFireSchedulers, + FirebaseApps, + ...deps, + ], + } + ]); +} diff --git a/src/messaging/messaging.spec.ts b/src/messaging/messaging.spec.ts index 8ff1fdc72..5a48de626 100644 --- a/src/messaging/messaging.spec.ts +++ b/src/messaging/messaging.spec.ts @@ -1,73 +1,39 @@ import { TestBed } from '@angular/core/testing'; -import { AngularFireModule, FIREBASE_APP_NAME, FIREBASE_OPTIONS, FirebaseApp } from '@angular/fire'; -import { AngularFireMessaging, AngularFireMessagingModule } from './public_api'; +import { getApp, initializeApp, provideFirebaseApp } from '@angular/fire/app'; +import { Messaging, getMessaging, isSupported, provideMessaging } from '@angular/fire/messaging'; import { COMMON_CONFIG } from '../test-config'; -import { rando } from '../firestore/utils.spec'; - -describe('AngularFireMessaging', () => { - let app: FirebaseApp; - let afm: AngularFireMessaging; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - AngularFireModule.initializeApp(COMMON_CONFIG, rando()), - AngularFireMessagingModule - ] - }); - - app = TestBed.inject(FirebaseApp); - afm = TestBed.inject(AngularFireMessaging); - }); - - afterEach(() => { - app.delete(); - }); - - it('should be exist', () => { - expect(afm instanceof AngularFireMessaging).toBe(true); - }); - - it('should have the FCM instance', () => { - expect(afm.deleteToken).toBeDefined(); - }); - -}); - -const FIREBASE_APP_NAME_TOO = (Math.random() + 1).toString(36).substring(7); - -describe('AngularFireMessaging with different app', () => { - let app: FirebaseApp; - let afm: AngularFireMessaging; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - AngularFireModule.initializeApp(COMMON_CONFIG, rando()), - AngularFireMessagingModule - ], - providers: [ - { provide: FIREBASE_APP_NAME, useValue: FIREBASE_APP_NAME_TOO }, - { provide: FIREBASE_OPTIONS, useValue: COMMON_CONFIG } - ] - }); - - app = TestBed.inject(FirebaseApp); - afm = TestBed.inject(AngularFireMessaging); - }); - - afterEach(() => { - app.delete(); - }); - - describe('', () => { - - it('should be an AngularFireMessaging type', () => { - expect(afm instanceof AngularFireMessaging).toEqual(true); +import { rando } from '../utils'; + +describe('Messaging', () => { + let messaging: Messaging; + let providedMessaging: Messaging; + let appName: string; + + describe('single injection', () => { + + beforeEach(() => { + appName = rando(); + TestBed.configureTestingModule({ + providers: [ + provideFirebaseApp(() => initializeApp(COMMON_CONFIG, appName)), + provideMessaging(() => { + providedMessaging = getMessaging(getApp(appName)); + return providedMessaging; + }), + ], + }); + messaging = TestBed.inject(Messaging); }); - it('should have the FCM instance', () => { - expect(afm.deleteToken).toBeDefined(); + it('should be injectable', async () => { + const supported = await TestBed.runInInjectionContext(isSupported); + if (supported) { + expect(providedMessaging).toBeTruthy(); + expect(messaging).toEqual(providedMessaging); + } else { + expect(providedMessaging).toBeUndefined(); + expect(messaging).toBeNull(); + } }); }); diff --git a/src/messaging/messaging.ts b/src/messaging/messaging.ts index 225d3dc61..f5f6249de 100644 --- a/src/messaging/messaging.ts +++ b/src/messaging/messaging.ts @@ -1,107 +1,30 @@ -import { Inject, Injectable, NgZone, Optional, PLATFORM_ID } from '@angular/core'; -import { messaging } from 'firebase/app'; -import firebase from 'firebase/app'; -import { concat, EMPTY, Observable, of, throwError, fromEvent } from 'rxjs'; -import { catchError, defaultIfEmpty, map, mergeMap, observeOn, switchMap, switchMapTo, tap, shareReplay, filter } from 'rxjs/operators'; -import { - FIREBASE_APP_NAME, - FIREBASE_OPTIONS, - FirebaseAppConfig, - FirebaseOptions, - ÉĩAngularFireSchedulers, - ÉĩfirebaseAppFactory, - ÉĩlazySDKProxy, - ÉĩPromiseProxy -} from '@angular/fire'; -import { isPlatformServer } from '@angular/common'; - -export interface AngularFireMessaging extends Omit<ÉĩPromiseProxy, 'deleteToken' | 'getToken' | 'requestPermission'> { +import { ÉĩgetAllInstancesOf } from '@angular/fire'; +import { Messaging as FirebaseMessaging } from 'firebase/messaging'; +import { from, timer } from 'rxjs'; +import { concatMap, distinct } from 'rxjs/operators'; + +// see notes in core/firebase.app.module.ts for why we're building the class like this + +export interface Messaging extends FirebaseMessaging {} + +export class Messaging { + constructor(messaging: FirebaseMessaging) { + return messaging; + } } -@Injectable({ - providedIn: 'any' -}) -export class AngularFireMessaging { - - public readonly requestPermission: Observable; - public readonly getToken: Observable; - public readonly tokenChanges: Observable; - public readonly messages: Observable<{}>; - public readonly requestToken: Observable; - public readonly deleteToken: (token: string) => Observable; - - constructor( - @Inject(FIREBASE_OPTIONS) options: FirebaseOptions, - @Optional() @Inject(FIREBASE_APP_NAME) nameOrConfig: string | FirebaseAppConfig | null | undefined, - // tslint:disable-next-line:ban-types - @Inject(PLATFORM_ID) platformId: Object, - zone: NgZone - ) { - const schedulers = new ÉĩAngularFireSchedulers(zone); - - const messaging = of(undefined).pipe( - observeOn(schedulers.outsideAngular), - switchMap(() => isPlatformServer(platformId) ? EMPTY : import('firebase/messaging')), - tap((it: any) => it), // It seems I need to touch the import for it to do anything... race maybe? - map(() => ÉĩfirebaseAppFactory(options, zone, nameOrConfig)), - map(app => app.messaging()), - shareReplay({ bufferSize: 1, refCount: false }) - ); - - this.requestPermission = messaging.pipe( - observeOn(schedulers.outsideAngular), - // tslint:disable-next-line - switchMap(messaging => firebase.messaging.isSupported() ? messaging.requestPermission() : throwError('Not supported.')) - ); - - this.getToken = messaging.pipe( - observeOn(schedulers.outsideAngular), - switchMap(messaging => firebase.messaging.isSupported() && Notification.permission === 'granted' ? messaging.getToken() : EMPTY), - defaultIfEmpty(null) - ); - - const tokenChanges = messaging.pipe( - observeOn(schedulers.outsideAngular), - switchMap(messaging => firebase.messaging.isSupported() ? new Observable(emitter => - messaging.onTokenRefresh(emitter.next, emitter.error, emitter.complete) - ) : EMPTY), - switchMapTo(this.getToken) - ); - - this.tokenChanges = messaging.pipe( - observeOn(schedulers.outsideAngular), - switchMap(messaging => firebase.messaging.isSupported() ? concat(this.getToken, tokenChanges) : EMPTY) - ); +export const MESSAGING_PROVIDER_NAME = 'messaging'; - // TODO 6.1 add observable for clicks - if (isPlatformServer(platformId)) { + +export interface MessagingInstances extends Array {} - this.messages = EMPTY; - - } else { - - this.messages = fromEvent(navigator.serviceWorker, 'message').pipe( - map((event: MessageEvent) => event.data.firebaseMessaging), - filter((message: any) => !!message && message.type === 'push-received'), - map((message: any) => message.payload), - filter((payload: any) => !!payload) - ); - - } - - this.requestToken = of(undefined).pipe( - switchMap(() => this.requestPermission), - catchError(() => of(null)), - mergeMap(() => this.tokenChanges) - ); - - this.deleteToken = (token: string) => messaging.pipe( - observeOn(schedulers.outsideAngular), - switchMap(messaging => messaging.deleteToken(token)), - defaultIfEmpty(false) - ); - - return ÉĩlazySDKProxy(this, messaging, zone); +export class MessagingInstances { + constructor() { + return ÉĩgetAllInstancesOf(MESSAGING_PROVIDER_NAME); } - } + +export const messagingInstance$ = timer(0, 300).pipe( + concatMap(() => from(ÉĩgetAllInstancesOf(MESSAGING_PROVIDER_NAME))), + distinct(), +); diff --git a/src/messaging/ng-package.json b/src/messaging/ng-package.json new file mode 100644 index 000000000..d3032da35 --- /dev/null +++ b/src/messaging/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/messaging/package.json b/src/messaging/package.json index a559c462f..8dce418a5 100644 --- a/src/messaging/package.json +++ b/src/messaging/package.json @@ -1,11 +1,3 @@ { - "$schema": "../../node_modules/ng-packagr/package.schema.json", - "ngPackage": { - "lib": { - "entryFile": "public_api.ts", - "umdModuleIds": { - "firebase/app": "firebase" - } - } - } + "$schema": "../../node_modules/ng-packagr/package.schema.json" } diff --git a/src/messaging/public_api.ts b/src/messaging/public_api.ts index 90df6c649..f0dd1128b 100644 --- a/src/messaging/public_api.ts +++ b/src/messaging/public_api.ts @@ -1,2 +1,3 @@ -export * from './messaging'; -export * from './messaging.module'; +export { MessagingInstances, Messaging, messagingInstance$ } from './messaging'; +export { provideMessaging, MessagingModule } from './messaging.module'; +export * from './firebase'; diff --git a/src/ng-package.json b/src/ng-package.json new file mode 100644 index 000000000..0eb72fb12 --- /dev/null +++ b/src/ng-package.json @@ -0,0 +1,12 @@ +{ + "$schema": "./node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + }, + "dest": "../dist/packages-dist", + "allowedNonPeerDependencies": [ + "@angular-devkit/architect", "@angular-devkit/core", + "@angular-devkit/schematics", "@schematics/angular", + "jsonc-parser", "firebase", "rxfire" + ] +} diff --git a/src/package.json b/src/package.json index 9b056aefe..47042a380 100644 --- a/src/package.json +++ b/src/package.json @@ -2,9 +2,13 @@ "$schema": "../node_modules/ng-packagr/package.schema.json", "name": "@angular/fire", "version": "ANGULARFIRE2_VERSION", - "description": "The official library for Firebase and Angular", - "schematics": "./collection.json", - "builders": "./builders.json", + "description": "Angular + Firebase = â¤ī¸", + "publishConfig": { + "registry": "https://wombat-dressing-room.appspot.com", + "access": "public" + }, + "schematics": "./schematics/collection.json", + "builders": "./schematics/builders.json", "keywords": [ "angular", "firebase", @@ -14,29 +18,39 @@ ], "repository": { "type": "git", - "url": "git+https://github.com/angular/angularfire2.git" + "url": "git+https://github.com/angular/angularfire.git" + }, + "bugs": { + "url": "https://github.com/angular/angularfire/issues" }, + "homepage": "https://github.com/angular/angularfire#readme", "author": "angular,firebase", "license": "MIT", "peerDependencies": { - "@angular/common": "ANGULAR_VERSION", - "@angular/core": "ANGULAR_VERSION", - "@angular/platform-browser": "ANGULAR_VERSION", - "@angular/platform-browser-dynamic": "ANGULAR_VERSION", - "firebase": "FIREBASE_VERSION", - "rxjs": "RXJS_VERSION" + "@angular/common": "^21.0.0", + "@angular/core": "^21.0.0", + "@angular/platform-browser": "^21.0.0", + "@angular/platform-server": "^21.0.0", + "rxjs": "~7.8.0", + "firebase-tools": "^14.0.0 || ^15.0.0", + "typescript": ">=5.8 <6.0" + }, + "peerDependenciesMeta": { + "firebase-tools": { "optional": true }, + "@angular/platform-server": { "optional": true }, + "typescript": { "optional": true } + }, + "dependencies": { + "firebase": "^12.4.0", + "rxfire": "^6.2.0", + "@angular-devkit/architect": ">= 0.2100.0 < 0.2200.0", + "@angular-devkit/core": "^21.0.0", + "@angular-devkit/schematics": "^21.0.0", + "@schematics/angular": "^21.0.0", + "jsonc-parser": "^3.0.0", + "tslib": "^2.3.0" }, - "ngPackage": { - "lib": { - "umdModuleIds": { - "@angular/core": "ng", - "@angular/common": "ng", - "firebase/app": "firebase", - "tslib": "tslib", - "rxjs": "rxjs" - }, - "entryFile": "core/public_api.ts" - }, - "dest": "../dist/packages-dist" + "ng-update": { + "migrations": "./schematics/migration.json" } -} \ No newline at end of file +} diff --git a/src/performance/firebase.ts b/src/performance/firebase.ts new file mode 100644 index 000000000..4f4b57011 --- /dev/null +++ b/src/performance/firebase.ts @@ -0,0 +1,12 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +export * from 'firebase/performance'; +import { ÉĩzoneWrap } from '@angular/fire'; +import { + getPerformance as _getPerformance, + initializePerformance as _initializePerformance, + trace as _trace +} from 'firebase/performance'; + +export const getPerformance = ÉĩzoneWrap(_getPerformance, true); +export const initializePerformance = ÉĩzoneWrap(_initializePerformance, true); +export const trace = ÉĩzoneWrap(_trace, true, 2); diff --git a/src/performance/ng-package.json b/src/performance/ng-package.json new file mode 100644 index 000000000..d3032da35 --- /dev/null +++ b/src/performance/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/performance/package.json b/src/performance/package.json index a559c462f..8dce418a5 100644 --- a/src/performance/package.json +++ b/src/performance/package.json @@ -1,11 +1,3 @@ { - "$schema": "../../node_modules/ng-packagr/package.schema.json", - "ngPackage": { - "lib": { - "entryFile": "public_api.ts", - "umdModuleIds": { - "firebase/app": "firebase" - } - } - } + "$schema": "../../node_modules/ng-packagr/package.schema.json" } diff --git a/src/performance/performance.module.ts b/src/performance/performance.module.ts index da1ac7d78..8bdbffad8 100644 --- a/src/performance/performance.module.ts +++ b/src/performance/performance.module.ts @@ -1,17 +1,91 @@ -import { NgModule, Optional } from '@angular/core'; -import { AngularFirePerformance } from './performance'; -import { PerformanceMonitoringService } from './performance.service'; +import { isPlatformBrowser } from '@angular/common'; +import { + EnvironmentProviders, + InjectionToken, + Injector, + NgModule, + NgZone, + Optional, + PLATFORM_ID, + makeEnvironmentProviders, +} from '@angular/core'; +import { VERSION, ÉĩAngularFireSchedulers, ÉĩgetDefaultInstanceOf } from '@angular/fire'; +import { FirebaseApp, FirebaseApps } from '@angular/fire/app'; +import { registerVersion } from 'firebase/app'; +import { FirebasePerformance } from 'firebase/performance'; +import { PERFORMANCE_PROVIDER_NAME, Performance, PerformanceInstances } from './performance'; + +export const PROVIDED_PERFORMANCE_INSTANCES = new InjectionToken('angularfire2.performance-instances'); + +export function defaultPerformanceInstanceFactory( + provided: FirebasePerformance[]|undefined, + defaultApp: FirebaseApp, + + platform: object +) { + if (!isPlatformBrowser(platform)) { return null; } + const defaultPerformance = ÉĩgetDefaultInstanceOf(PERFORMANCE_PROVIDER_NAME, provided, defaultApp); + return defaultPerformance && new Performance(defaultPerformance); +} + +export function performanceInstanceFactory(fn: (injector: Injector) => FirebasePerformance) { + + return (zone: NgZone, platform: object, injector: Injector) => { + if (!isPlatformBrowser(platform)) { return null; } + const performance = zone.runOutsideAngular(() => fn(injector)); + return new Performance(performance); + }; +} + +const PERFORMANCE_INSTANCES_PROVIDER = { + provide: PerformanceInstances, + deps: [ + [new Optional(), PROVIDED_PERFORMANCE_INSTANCES ], + ] +}; + +const DEFAULT_PERFORMANCE_INSTANCE_PROVIDER = { + provide: Performance, + useFactory: defaultPerformanceInstanceFactory, + deps: [ + [new Optional(), PROVIDED_PERFORMANCE_INSTANCES ], + FirebaseApp, + PLATFORM_ID, + ] +}; @NgModule({ - providers: [ AngularFirePerformance ] + providers: [ + DEFAULT_PERFORMANCE_INSTANCE_PROVIDER, + PERFORMANCE_INSTANCES_PROVIDER, + ] }) -export class AngularFirePerformanceModule { - constructor( - perf: AngularFirePerformance, - @Optional() _: PerformanceMonitoringService - ) { - // call anything here to get perf loading - // tslint:disable-next-line:no-unused-expression - perf.dataCollectionEnabled; +export class PerformanceModule { + constructor() { + registerVersion('angularfire', VERSION.full, 'perf'); } } + +export function providePerformance( + fn: (injector: Injector) => FirebasePerformance, ...deps: any[] +): EnvironmentProviders { + registerVersion('angularfire', VERSION.full, 'perf'); + + return makeEnvironmentProviders([ + DEFAULT_PERFORMANCE_INSTANCE_PROVIDER, + PERFORMANCE_INSTANCES_PROVIDER, + { + provide: PROVIDED_PERFORMANCE_INSTANCES, + useFactory: performanceInstanceFactory(fn), + multi: true, + deps: [ + NgZone, + PLATFORM_ID, + Injector, + ÉĩAngularFireSchedulers, + FirebaseApps, + ...deps, + ] + } + ]); +} diff --git a/src/performance/performance.spec.ts b/src/performance/performance.spec.ts index c07832f3e..e8264d941 100644 --- a/src/performance/performance.spec.ts +++ b/src/performance/performance.spec.ts @@ -1,35 +1,35 @@ import { TestBed } from '@angular/core/testing'; -import { AngularFireModule, FirebaseApp } from '@angular/fire'; -import { AngularFirePerformance, AngularFirePerformanceModule } from './public_api'; +import { FirebaseApp, initializeApp, provideFirebaseApp } from '@angular/fire/app'; +import { Performance, getPerformance, providePerformance } from '@angular/fire/performance'; import { COMMON_CONFIG } from '../test-config'; -import { rando } from '../firestore/utils.spec'; -describe('AngularFirePerformance', () => { +describe('Performance', () => { let app: FirebaseApp; - let afp: AngularFirePerformance; + let performance: Performance; + let providedPerformance: Performance; - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - AngularFireModule.initializeApp(COMMON_CONFIG, rando()), - AngularFirePerformanceModule - ] - }); - - app = TestBed.inject(FirebaseApp); - afp = TestBed.inject(AngularFirePerformance); - }); + describe('single injection', () => { - afterEach(() => { - app.delete(); - }); + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + provideFirebaseApp(() => initializeApp(COMMON_CONFIG)), + providePerformance(() => { + providedPerformance = getPerformance(); + return providedPerformance; + }), + ], + }); + app = TestBed.inject(FirebaseApp); + performance = TestBed.inject(Performance); + }); - it('should exist', () => { - expect(afp instanceof AngularFirePerformance).toBe(true); - }); + it('should be injectable', () => { + expect(providedPerformance).toBeTruthy(); + expect(performance).toEqual(providedPerformance); + expect(performance.app).toEqual(app); + }); - it('should have the Performance instance', () => { - expect(afp.dataCollectionEnabled).toBeDefined(); }); }); diff --git a/src/performance/performance.ts b/src/performance/performance.ts index d60463ddc..f49c8f5ea 100644 --- a/src/performance/performance.ts +++ b/src/performance/performance.ts @@ -1,153 +1,30 @@ -import { Inject, Injectable, InjectionToken, NgZone, Optional, PLATFORM_ID } from '@angular/core'; -import { EMPTY, Observable, of, Subscription } from 'rxjs'; -import { map, shareReplay, switchMap, tap } from 'rxjs/operators'; -import { performance } from 'firebase/app'; -import { FirebaseApp, ÉĩlazySDKProxy, ÉĩPromiseProxy } from '@angular/fire'; -import { isPlatformBrowser } from '@angular/common'; -import firebase from 'firebase/app'; - -// SEMVER @ v6, drop and move core ng metrics to a service -export const AUTOMATICALLY_TRACE_CORE_NG_METRICS = new InjectionToken('angularfire2.performance.auto_trace'); -export const INSTRUMENTATION_ENABLED = new InjectionToken('angularfire2.performance.instrumentationEnabled'); -export const DATA_COLLECTION_ENABLED = new InjectionToken('angularfire2.performance.dataCollectionEnabled'); - -export interface AngularFirePerformance extends ÉĩPromiseProxy { +import { ÉĩgetAllInstancesOf } from '@angular/fire'; +import { FirebasePerformance } from 'firebase/performance'; +import { from, timer } from 'rxjs'; +import { concatMap, distinct } from 'rxjs/operators'; + +// see notes in core/firebase.app.module.ts for why we're building the class like this + +export interface Performance extends FirebasePerformance {} + +export class Performance { + constructor(performance: FirebasePerformance) { + return performance; + } } -@Injectable({ - providedIn: 'any' -}) -export class AngularFirePerformance { - - private readonly performance: Observable; +export const PERFORMANCE_PROVIDER_NAME = 'performance'; - constructor( - app: FirebaseApp, - @Optional() @Inject(INSTRUMENTATION_ENABLED) instrumentationEnabled: boolean | null, - @Optional() @Inject(DATA_COLLECTION_ENABLED) dataCollectionEnabled: boolean | null, - private zone: NgZone, - // tslint:disable-next-line:ban-types - @Inject(PLATFORM_ID) platformId: Object - ) { - - this.performance = of(undefined).pipe( - switchMap(() => isPlatformBrowser(platformId) ? zone.runOutsideAngular(() => import('firebase/performance')) : EMPTY), - switchMap(() => import('@firebase/performance')), - tap(perf => perf.registerPerformance && perf.registerPerformance(firebase as any)), - map(() => zone.runOutsideAngular(() => app.performance())), - tap(performance => { - if (instrumentationEnabled !== true) { - performance.instrumentationEnabled = false; - } - if (dataCollectionEnabled !== true) { - performance.dataCollectionEnabled = false; - } - }), - shareReplay({ bufferSize: 1, refCount: false }) - ); - - return ÉĩlazySDKProxy(this, this.performance, zone); + +export interface PerformanceInstances extends Array {} +export class PerformanceInstances { + constructor() { + return ÉĩgetAllInstancesOf(PERFORMANCE_PROVIDER_NAME); } - } -const trace$ = (traceId: string) => { - if (typeof window !== 'undefined' && window.performance) { - const entries = window.performance.getEntriesByName(traceId, 'measure') || []; - const startMarkName = `_${traceId}Start[${entries.length}]`; - const endMarkName = `_${traceId}End[${entries.length}]`; - return new Observable(emitter => { - window.performance.mark(startMarkName); - emitter.next(); - return { - unsubscribe: () => { - window.performance.mark(endMarkName); - window.performance.measure(traceId, startMarkName, endMarkName); - } - }; - }); - } else { - return EMPTY; - } -}; - -export const traceUntil = ( - name: string, - test: (a: T) => boolean, - options?: { orComplete?: boolean } -) => (source$: Observable) => new Observable(subscriber => { - const traceSubscription = trace$(name).subscribe(); - return source$.pipe( - tap( - a => test(a) && traceSubscription.unsubscribe(), - () => { - }, - () => options && options.orComplete && traceSubscription.unsubscribe() - ) - ).subscribe(subscriber); -}); - -export const traceWhile = ( - name: string, - test: (a: T) => boolean, - options?: { orComplete?: boolean } -) => (source$: Observable) => new Observable(subscriber => { - let traceSubscription: Subscription | undefined; - return source$.pipe( - tap( - a => { - if (test(a)) { - traceSubscription = traceSubscription || trace$(name).subscribe(); - } else { - if (traceSubscription) { - traceSubscription.unsubscribe(); - } - - traceSubscription = undefined; - } - }, - () => { - }, - () => options && options.orComplete && traceSubscription && traceSubscription.unsubscribe() - ) - ).subscribe(subscriber); -}); - -export const traceUntilComplete = (name: string) => (source$: Observable) => new Observable(subscriber => { - const traceSubscription = trace$(name).subscribe(); - return source$.pipe( - tap( - () => { - }, - () => { - }, - () => traceSubscription.unsubscribe() - ) - ).subscribe(subscriber); -}); - -export const traceUntilFirst = (name: string) => (source$: Observable) => new Observable(subscriber => { - const traceSubscription = trace$(name).subscribe(); - return source$.pipe( - tap( - () => traceSubscription.unsubscribe(), - () => { - }, - () => { - } - ) - ).subscribe(subscriber); -}); - -export const trace = (name: string) => (source$: Observable) => new Observable(subscriber => { - const traceSubscription = trace$(name).subscribe(); - return source$.pipe( - tap( - () => traceSubscription.unsubscribe(), - () => { - }, - () => traceSubscription.unsubscribe() - ) - ).subscribe(subscriber); -}); +export const performanceInstance$ = timer(0, 300).pipe( + concatMap(() => from(ÉĩgetAllInstancesOf(PERFORMANCE_PROVIDER_NAME))), + distinct(), +); diff --git a/src/performance/public_api.ts b/src/performance/public_api.ts index b6dc28449..b0698ba0a 100644 --- a/src/performance/public_api.ts +++ b/src/performance/public_api.ts @@ -1,3 +1,4 @@ -export * from './performance'; -export * from './performance.module'; -export * from './performance.service'; +export { Performance, PerformanceInstances, performanceInstance$ } from './performance'; +export { providePerformance, PerformanceModule } from './performance.module'; +export * from './rxfire'; +export * from './firebase'; diff --git a/src/performance/rxfire.ts b/src/performance/rxfire.ts new file mode 100644 index 000000000..b81aa9f95 --- /dev/null +++ b/src/performance/rxfire.ts @@ -0,0 +1,13 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +import { ÉĩzoneWrap } from '@angular/fire'; +import { + traceUntil as _traceUntil, + traceUntilComplete as _traceUntilComplete, + traceUntilFirst as _traceUntilFirst, + traceWhile as _traceWhile +} from 'rxfire/performance'; + +export const traceUntil = ÉĩzoneWrap(_traceUntil, true); +export const traceUntilComplete = ÉĩzoneWrap(_traceUntilComplete, true); +export const traceUntilFirst = ÉĩzoneWrap(_traceUntilFirst, true); +export const traceWhile = ÉĩzoneWrap(_traceWhile, true); diff --git a/src/public_api.ts b/src/public_api.ts new file mode 100644 index 000000000..e54a8df63 --- /dev/null +++ b/src/public_api.ts @@ -0,0 +1,2 @@ +export * from './core'; +export * from './zones'; diff --git a/src/remote-config/firebase.ts b/src/remote-config/firebase.ts new file mode 100644 index 000000000..4b5540b2f --- /dev/null +++ b/src/remote-config/firebase.ts @@ -0,0 +1,34 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +export * from 'firebase/remote-config'; +import { ÉĩzoneWrap } from '@angular/fire'; +import { + activate as _activate, + ensureInitialized as _ensureInitialized, + fetchAndActivate as _fetchAndActivate, + fetchConfig as _fetchConfig, + getAll as _getAll, + getBoolean as _getBoolean, + getNumber as _getNumber, + getRemoteConfig as _getRemoteConfig, + getString as _getString, + getValue as _getValue, + isSupported as _isSupported, + onConfigUpdate as _onConfigUpdate, + setCustomSignals as _setCustomSignals, + setLogLevel as _setLogLevel +} from 'firebase/remote-config'; + +export const activate = ÉĩzoneWrap(_activate, true); +export const ensureInitialized = ÉĩzoneWrap(_ensureInitialized, true); +export const fetchAndActivate = ÉĩzoneWrap(_fetchAndActivate, true); +export const fetchConfig = ÉĩzoneWrap(_fetchConfig, true); +export const getAll = ÉĩzoneWrap(_getAll, true); +export const getBoolean = ÉĩzoneWrap(_getBoolean, true); +export const getNumber = ÉĩzoneWrap(_getNumber, true); +export const getRemoteConfig = ÉĩzoneWrap(_getRemoteConfig, true); +export const getString = ÉĩzoneWrap(_getString, true); +export const getValue = ÉĩzoneWrap(_getValue, true); +export const isSupported = ÉĩzoneWrap(_isSupported, true); +export const onConfigUpdate = ÉĩzoneWrap(_onConfigUpdate, true); +export const setCustomSignals = ÉĩzoneWrap(_setCustomSignals, true); +export const setLogLevel = ÉĩzoneWrap(_setLogLevel, true); diff --git a/src/remote-config/ng-package.json b/src/remote-config/ng-package.json new file mode 100644 index 000000000..d3032da35 --- /dev/null +++ b/src/remote-config/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/remote-config/package.json b/src/remote-config/package.json index aad6cb7e4..8dce418a5 100644 --- a/src/remote-config/package.json +++ b/src/remote-config/package.json @@ -1,11 +1,3 @@ { - "$schema": "../../node_modules/ng-packagr/package.schema.json", - "ngPackage": { - "lib": { - "umdModuleIds": { - "firebase/app": "firebase" - }, - "entryFile": "public_api.ts" - } - } -} \ No newline at end of file + "$schema": "../../node_modules/ng-packagr/package.schema.json" +} diff --git a/src/remote-config/public_api.ts b/src/remote-config/public_api.ts index f3239f665..fd4c1896a 100644 --- a/src/remote-config/public_api.ts +++ b/src/remote-config/public_api.ts @@ -1,2 +1,4 @@ -export * from './remote-config'; -export * from './remote-config.module'; +export { RemoteConfigInstances, RemoteConfig, remoteConfigInstance$ } from './remote-config'; +export { provideRemoteConfig, RemoteConfigModule } from './remote-config.module'; +export * from './rxfire'; +export * from './firebase'; diff --git a/src/remote-config/remote-config.module.ts b/src/remote-config/remote-config.module.ts index 0c8392d72..866b505e0 100644 --- a/src/remote-config/remote-config.module.ts +++ b/src/remote-config/remote-config.module.ts @@ -1,7 +1,89 @@ -import { NgModule } from '@angular/core'; -import { AngularFireRemoteConfig } from './remote-config'; +import { isPlatformServer } from '@angular/common'; +import { + EnvironmentProviders, + InjectionToken, + Injector, + NgModule, + NgZone, + Optional, + PLATFORM_ID, + makeEnvironmentProviders, +} from '@angular/core'; +import { VERSION, ÉĩAngularFireSchedulers, ÉĩgetDefaultInstanceOf } from '@angular/fire'; +import { FirebaseApp, FirebaseApps } from '@angular/fire/app'; +import { registerVersion } from 'firebase/app'; +import { RemoteConfig as FirebaseRemoteConfig } from 'firebase/remote-config'; +import { REMOTE_CONFIG_PROVIDER_NAME, RemoteConfig, RemoteConfigInstances } from './remote-config'; + +export const PROVIDED_REMOTE_CONFIG_INSTANCES = new InjectionToken('angularfire2.remote-config-instances'); + +export function defaultRemoteConfigInstanceFactory( + provided: FirebaseRemoteConfig[]|undefined, + defaultApp: FirebaseApp, + platformId: object, +) { + if (isPlatformServer(platformId)) { return null; } + const defaultRemoteConfig = ÉĩgetDefaultInstanceOf(REMOTE_CONFIG_PROVIDER_NAME, provided, defaultApp); + return defaultRemoteConfig && new RemoteConfig(defaultRemoteConfig); +} + +export function remoteConfigInstanceFactory(fn: (injector: Injector) => FirebaseRemoteConfig) { + return (zone: NgZone, injector: Injector, platformId: object) => { + if (isPlatformServer(platformId)) { return null; } + const remoteConfig = zone.runOutsideAngular(() => fn(injector)); + return new RemoteConfig(remoteConfig); + }; +} + +const REMOTE_CONFIG_INSTANCES_PROVIDER = { + provide: RemoteConfigInstances, + deps: [ + [new Optional(), PROVIDED_REMOTE_CONFIG_INSTANCES ], + ] +}; + +const DEFAULT_REMOTE_CONFIG_INSTANCE_PROVIDER = { + provide: RemoteConfig, + useFactory: defaultRemoteConfigInstanceFactory, + deps: [ + [new Optional(), PROVIDED_REMOTE_CONFIG_INSTANCES ], + FirebaseApp, + PLATFORM_ID, + ] +}; @NgModule({ - providers: [AngularFireRemoteConfig] + providers: [ + DEFAULT_REMOTE_CONFIG_INSTANCE_PROVIDER, + REMOTE_CONFIG_INSTANCES_PROVIDER, + ] }) -export class AngularFireRemoteConfigModule { } +export class RemoteConfigModule { + constructor() { + registerVersion('angularfire', VERSION.full, 'rc'); + } +} + +export function provideRemoteConfig( + fn: (injector: Injector) => FirebaseRemoteConfig, ...deps: any[] +): EnvironmentProviders { + registerVersion('angularfire', VERSION.full, 'rc'); + + return makeEnvironmentProviders([ + DEFAULT_REMOTE_CONFIG_INSTANCE_PROVIDER, + REMOTE_CONFIG_INSTANCES_PROVIDER, + { + provide: PROVIDED_REMOTE_CONFIG_INSTANCES, + useFactory: remoteConfigInstanceFactory(fn), + multi: true, + deps: [ + NgZone, + Injector, + PLATFORM_ID, + ÉĩAngularFireSchedulers, + FirebaseApps, + ...deps, + ] + } + ]); +} diff --git a/src/remote-config/remote-config.spec.ts b/src/remote-config/remote-config.spec.ts index aa3a87924..fc2842062 100644 --- a/src/remote-config/remote-config.spec.ts +++ b/src/remote-config/remote-config.spec.ts @@ -1,75 +1,36 @@ import { TestBed } from '@angular/core/testing'; -import { AngularFireModule, FIREBASE_APP_NAME, FIREBASE_OPTIONS, FirebaseApp } from '@angular/fire'; -import { AngularFireRemoteConfig, AngularFireRemoteConfigModule, DEFAULTS, SETTINGS } from './public_api'; +import { FirebaseApp, getApp, initializeApp, provideFirebaseApp } from '@angular/fire/app'; +import { RemoteConfig, getRemoteConfig, provideRemoteConfig } from '@angular/fire/remote-config'; import { COMMON_CONFIG } from '../test-config'; -import { rando } from '../firestore/utils.spec'; +import { rando } from '../utils'; -describe('AngularFireRemoteConfig', () => { +describe('RemoteConfig', () => { let app: FirebaseApp; - let rc: AngularFireRemoteConfig; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - AngularFireModule.initializeApp(COMMON_CONFIG, rando()), - AngularFireRemoteConfigModule - ] - }); - - app = TestBed.inject(FirebaseApp); - rc = TestBed.inject(AngularFireRemoteConfig); - }); - - afterEach(() => { - app.delete(); - }); - - it('should be exist', () => { - expect(rc instanceof AngularFireRemoteConfig).toBe(true); - }); - - it('should have the Firebase Functions instance', () => { - expect(rc.getValue).toBeDefined(); - }); - -}); - -const FIREBASE_APP_NAME_TOO = (Math.random() + 1).toString(36).substring(7); - -describe('AngularFireRemoteConfig with different app', () => { - let app: FirebaseApp; - let rc: AngularFireRemoteConfig; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - AngularFireModule.initializeApp(COMMON_CONFIG, rando()), - AngularFireRemoteConfigModule - ], - providers: [ - { provide: FIREBASE_APP_NAME, useValue: FIREBASE_APP_NAME_TOO }, - { provide: FIREBASE_OPTIONS, useValue: COMMON_CONFIG }, - { provide: SETTINGS, useValue: {} }, - { provide: DEFAULTS, useValue: {} } - ] - }); - - app = TestBed.inject(FirebaseApp); - rc = TestBed.inject(AngularFireRemoteConfig); - }); - - afterEach(() => { - app.delete(); - }); - - describe('', () => { - - it('should be an AngularFireAuth type', () => { - expect(rc instanceof AngularFireRemoteConfig).toEqual(true); + let remoteConfig: RemoteConfig; + let providedRemoteConfig: RemoteConfig; + let appName: string; + + describe('single injection', () => { + + beforeEach(() => { + appName = rando(); + TestBed.configureTestingModule({ + providers: [ + provideFirebaseApp(() => initializeApp(COMMON_CONFIG, appName)), + provideRemoteConfig(() => { + providedRemoteConfig = getRemoteConfig(getApp(appName)); + return providedRemoteConfig; + }), + ], + }); + app = TestBed.inject(FirebaseApp); + remoteConfig = TestBed.inject(RemoteConfig); }); - it('should have the Firebase Functions instance', () => { - expect(rc.getValue).toBeDefined(); + it('should be injectable', () => { + expect(providedRemoteConfig).toBeTruthy(); + expect(remoteConfig).toEqual(providedRemoteConfig); + expect(remoteConfig.app).toEqual(app); }); }); diff --git a/src/remote-config/remote-config.ts b/src/remote-config/remote-config.ts index 0fbeb10f1..d3e164b75 100644 --- a/src/remote-config/remote-config.ts +++ b/src/remote-config/remote-config.ts @@ -1,312 +1,30 @@ -import { Inject, Injectable, InjectionToken, NgZone, Optional, PLATFORM_ID } from '@angular/core'; -import { concat, EMPTY, MonoTypeOperatorFunction, Observable, of, OperatorFunction, pipe } from 'rxjs'; -import { - debounceTime, - distinctUntilChanged, - filter, - groupBy, - map, - mergeMap, - observeOn, - scan, - shareReplay, - startWith, - switchMap, - tap, - withLatestFrom -} from 'rxjs/operators'; -import { - FIREBASE_APP_NAME, - FIREBASE_OPTIONS, - FirebaseAppConfig, - FirebaseOptions, - ÉĩAngularFireSchedulers, - ÉĩfirebaseAppFactory, - ÉĩkeepUnstableUntilFirstFactory, - ÉĩlazySDKProxy, - ÉĩPromiseProxy -} from '@angular/fire'; -import { remoteConfig } from 'firebase/app'; -import { isPlatformBrowser } from '@angular/common'; -import firebase from 'firebase/app'; +import { ÉĩgetAllInstancesOf } from '@angular/fire'; +import { RemoteConfig as FirebaseRemoteConfig } from 'firebase/remote-config'; +import { from, timer } from 'rxjs'; +import { concatMap, distinct } from 'rxjs/operators'; -export interface ConfigTemplate { - [key: string]: string | number | boolean; -} - -export const SETTINGS = new InjectionToken('angularfire2.remoteConfig.settings'); -export const DEFAULTS = new InjectionToken('angularfire2.remoteConfig.defaultConfig'); - -export interface AngularFireRemoteConfig extends ÉĩPromiseProxy { -} - -const AS_TO_FN = { strings: 'asString', numbers: 'asNumber', booleans: 'asBoolean' }; -const STATIC_VALUES = { numbers: 0, booleans: false, strings: undefined }; - -// TODO look into the types here, I don't like the anys -const proxyAll = (observable: Observable, as: 'numbers' | 'booleans' | 'strings') => new Proxy( - observable.pipe(mapToObject(as as any)), { - get: (self, name: string) => self[name] || observable.pipe( - map(all => all.find(p => p.key === name)), - map(param => param ? param[AS_TO_FN[as]]() : STATIC_VALUES[as]), - distinctUntilChanged() - ) - } -) as any; - -// TODO export as implements Partial<...> so minor doesn't break us -export class Value implements remoteConfig.Value { - asBoolean() { - return ['1', 'true', 't', 'y', 'yes', 'on'].indexOf(this._value.toLowerCase()) > -1; - } - - asString() { - return this._value; - } - - asNumber() { - return Number(this._value) || 0; - } - - getSource() { - return this._source; - } - - // tslint:disable-next-line:variable-name - constructor(public _source: remoteConfig.ValueSource, public _value: string) { - } -} +// see notes in core/firebase.app.module.ts for why we're building the class like this + +export interface RemoteConfig extends FirebaseRemoteConfig {} -// SEMVER use ConstructorParameters when we can support Typescript 3.6 -export class Parameter extends Value { - constructor(public key: string, public fetchTimeMillis: number, source: remoteConfig.ValueSource, value: string) { - super(source, value); +export class RemoteConfig { + constructor(remoteConfig: FirebaseRemoteConfig) { + return remoteConfig; } } -// If it's a Parameter array, test any, else test the individual Parameter -const filterTest = (fn: (param: Parameter) => boolean) => filter(it => Array.isArray(it) ? it.some(fn) : fn(it)); - -// Allow the user to bypass the default values and wait till they get something from the server, even if it's a cached copy; -// if used in conjuntion with first() it will only fetch RC values from the server if they aren't cached locally -export const filterRemote = () => filterTest(p => p.getSource() === 'remote'); - -// filterFresh allows the developer to effectively set up a maximum cache time -export const filterFresh = (howRecentInMillis: number) => filterTest(p => p.fetchTimeMillis + howRecentInMillis >= new Date().getTime()); - - -// I ditched loading the defaults into RC and a simple map for scan since we already have our own defaults implementation. -// The idea here being that if they have a default that never loads from the server, they will be able to tell via fetchTimeMillis -// on the Parameter. Also if it doesn't come from the server it won't emit again in .changes, due to the distinctUntilChanged, -// which we can simplify to === rather than deep comparison -const scanToParametersArray = ( - remoteConfig: Observable -): OperatorFunction<{ [key: string]: remoteConfig.Value }, Parameter[]> => pipe( - withLatestFrom(remoteConfig), - scan((existing, [all, rc]) => { - // SEMVER use "new Set" to unique once we're only targeting es6 - // at the scale we expect remote config to be at, we probably won't see a performance hit from this unoptimized uniqueness - // implementation. - // const allKeys = [...new Set([...existing.map(p => p.key), ...Object.keys(all)])]; - const allKeys = [...existing.map(p => p.key), ...Object.keys(all)].filter((v, i, a) => a.indexOf(v) === i); - return allKeys.map(key => { - const updatedValue = all[key]; - return updatedValue ? new Parameter(key, rc ? rc.fetchTimeMillis : -1, updatedValue.getSource(), updatedValue.asString()) - : existing.find(p => p.key === key); - }); - }, [] as Array) -); - +export const REMOTE_CONFIG_PROVIDER_NAME = 'remote-config'; -@Injectable({ - providedIn: 'any' -}) -export class AngularFireRemoteConfig { + +export interface RemoteConfigInstances extends Array {} - readonly changes: Observable; - readonly parameters: Observable; - readonly numbers: Observable<{ [key: string]: number | undefined }> & { [key: string]: Observable }; - readonly booleans: Observable<{ [key: string]: boolean | undefined }> & { [key: string]: Observable }; - readonly strings: Observable<{ [key: string]: string | undefined }> & { [key: string]: Observable }; - - constructor( - @Inject(FIREBASE_OPTIONS) options: FirebaseOptions, - @Optional() @Inject(FIREBASE_APP_NAME) nameOrConfig: string | FirebaseAppConfig | null | undefined, - @Optional() @Inject(SETTINGS) settings: remoteConfig.Settings | null, - @Optional() @Inject(DEFAULTS) defaultConfig: ConfigTemplate | null, - private zone: NgZone, - // tslint:disable-next-line:ban-types - @Inject(PLATFORM_ID) platformId: Object - ) { - - const schedulers = new ÉĩAngularFireSchedulers(zone); - - const remoteConfig$ = of(undefined).pipe( - observeOn(schedulers.outsideAngular), - switchMap(() => isPlatformBrowser(platformId) ? import('firebase/remote-config') : EMPTY), - switchMap(() => import('@firebase/remote-config')), - tap(rc => rc.registerRemoteConfig && rc.registerRemoteConfig(firebase as any)), - map(() => ÉĩfirebaseAppFactory(options, zone, nameOrConfig)), - map(app => app.remoteConfig()), - tap(rc => { - if (settings) { - rc.settings = settings; - } - if (defaultConfig) { - rc.defaultConfig = defaultConfig; - } - }), - // tslint:disable-next-line - startWith(undefined), - shareReplay({ bufferSize: 1, refCount: false }) - ); - - const loadedRemoteConfig$ = remoteConfig$.pipe( - filter(rc => !!rc) - ); - - const default$: Observable<{ [key: string]: remoteConfig.Value }> = of(Object.keys(defaultConfig || {}).reduce( - (c, k) => ({ ...c, [k]: new Value('default', defaultConfig[k].toString()) }), {} - )); - - // we should filter out the defaults we provided to RC, since we have our own implementation - // that gives us a -1 for fetchTimeMillis (so filterFresh can filter them out) - const filterOutDefaults = map<{ [key: string]: remoteConfig.Value }, { [key: string]: remoteConfig.Value }>(all => - Object.keys(all) - .filter(key => all[key].getSource() !== 'default') - .reduce((acc, key) => ({ ...acc, [key]: all[key] }), {}) - ); - - const existing$ = loadedRemoteConfig$.pipe( - switchMap(rc => - rc.activate() - .then(() => rc.ensureInitialized()) - .then(() => rc.getAll()) - ), - filterOutDefaults - ); - - const fresh$ = loadedRemoteConfig$.pipe( - switchMap(rc => zone.runOutsideAngular(() => - rc.fetchAndActivate() - .then(() => rc.ensureInitialized()) - .then(() => rc.getAll()) - )), - filterOutDefaults - ); - - this.parameters = concat(default$, existing$, fresh$).pipe( - scanToParametersArray(remoteConfig$), - ÉĩkeepUnstableUntilFirstFactory(schedulers), - shareReplay({ bufferSize: 1, refCount: true }) - ); - - this.changes = this.parameters.pipe( - switchMap(params => of(...params)), - groupBy(param => param.key), - mergeMap(group => group.pipe( - distinctUntilChanged() - )) - ); - - this.strings = proxyAll(this.parameters, 'strings'); - this.booleans = proxyAll(this.parameters, 'booleans'); - this.numbers = proxyAll(this.parameters, 'numbers'); - - return ÉĩlazySDKProxy(this, loadedRemoteConfig$, zone); +export class RemoteConfigInstances { + constructor() { + return ÉĩgetAllInstancesOf(REMOTE_CONFIG_PROVIDER_NAME); } - -} - - -export const budget = (interval: number): MonoTypeOperatorFunction => (source: Observable) => new Observable(observer => { - let timedOut = false; - // TODO use scheduler task rather than settimeout - const timeout = setTimeout(() => { - observer.complete(); - timedOut = true; - }, interval); - return source.subscribe({ - next(val) { - if (!timedOut) { - observer.next(val); - } - }, - error(err) { - if (!timedOut) { - clearTimeout(timeout); - observer.error(err); - } - }, - complete() { - if (!timedOut) { - clearTimeout(timeout); - observer.complete(); - } - } - }); -}); - -const typedMethod = (it: any) => { - switch (typeof it) { - case 'string': - return 'asString'; - case 'boolean': - return 'asBoolean'; - case 'number': - return 'asNumber'; - default: - return 'asString'; - } -}; - - -export function scanToObject(): OperatorFunction; -export function scanToObject(to: 'numbers'): OperatorFunction; -export function scanToObject(to: 'booleans'): OperatorFunction; -// tslint:disable-next-line:unified-signatures -export function scanToObject(to: 'strings'): OperatorFunction; -export function scanToObject(template: T): OperatorFunction; -export function scanToObject(to: 'numbers' | 'booleans' | 'strings' | T = 'strings') { - return pipe( - // TODO cleanup - scan( - (c, p: Parameter) => ({ - ...c, [p.key]: typeof to === 'object' ? - p[typedMethod(to[p.key])]() : - p[AS_TO_FN[to]]() - }), - typeof to === 'object' ? - to as T & { [key: string]: string | undefined } : - {} as { [key: string]: number | boolean | string } - ), - debounceTime(1), - budget(10), - distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)) - ); -} - -export function mapToObject(): OperatorFunction; -export function mapToObject(to: 'numbers'): OperatorFunction; -export function mapToObject(to: 'booleans'): OperatorFunction; -// tslint:disable-next-line:unified-signatures -export function mapToObject(to: 'strings'): OperatorFunction; -export function mapToObject(template: T): - OperatorFunction; -export function mapToObject(to: 'numbers' | 'booleans' | 'strings' | T = 'strings') { - return pipe( - // TODO this is getting a little long, cleanup - map((params: Parameter[]) => params.reduce( - (c, p) => ({ - ...c, [p.key]: typeof to === 'object' ? - p[typedMethod(to[p.key])]() : - p[AS_TO_FN[to]]() - }), - typeof to === 'object' ? - to as T & { [key: string]: string | undefined } : - {} as { [key: string]: number | boolean | string } - )), - distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)) - ); } +export const remoteConfigInstance$ = timer(0, 300).pipe( + concatMap(() => from(ÉĩgetAllInstancesOf(REMOTE_CONFIG_PROVIDER_NAME))), + distinct(), +); diff --git a/src/remote-config/rxfire.ts b/src/remote-config/rxfire.ts new file mode 100644 index 000000000..450c624e1 --- /dev/null +++ b/src/remote-config/rxfire.ts @@ -0,0 +1,15 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +import { ÉĩzoneWrap } from '@angular/fire'; +import { + getAll as _getAll, + getBoolean as _getBoolean, + getNumber as _getNumber, + getString as _getString, + getValue as _getValue +} from 'rxfire/remote-config'; + +export const getAllChanges = ÉĩzoneWrap(_getAll, true); +export const getBooleanChanges = ÉĩzoneWrap(_getBoolean, true); +export const getNumberChanges = ÉĩzoneWrap(_getNumber, true); +export const getStringChanges = ÉĩzoneWrap(_getString, true); +export const getValueChanges = ÉĩzoneWrap(_getValue, true); diff --git a/src/root.spec.ts b/src/root.spec.ts deleted file mode 100644 index 36a35b732..000000000 --- a/src/root.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -// These paths are written to use the dist build -export * from './core/angularfire2.spec'; -export * from './analytics/analytics.spec'; -export * from './auth/auth.spec'; -export * from './auth-guard/auth-guard.spec'; -export * from './firestore/firestore.spec'; -export * from './firestore/document/document.spec'; -export * from './firestore/collection/collection.spec'; -export * from './firestore/collection-group/collection-group.spec'; -export * from './functions/functions.spec'; -export * from './database/database.spec'; -export * from './database/utils.spec'; -export * from './database/observable/fromRef.spec'; -export * from './database/list/changes.spec'; -export * from './database/list/snapshot-changes.spec'; -export * from './database/list/state-changes.spec'; -export * from './database/list/audit-trail.spec'; -export * from './messaging/messaging.spec'; -export * from './remote-config/remote-config.spec'; -export * from './storage/storage.spec'; -export * from './performance/performance.spec'; diff --git a/src/schematics/add/index.ts b/src/schematics/add/index.ts new file mode 100644 index 000000000..a2636952a --- /dev/null +++ b/src/schematics/add/index.ts @@ -0,0 +1,18 @@ +import { SchematicContext, Tree } from '@angular-devkit/schematics'; +import { NodePackageInstallTask, RunSchematicTask } from '@angular-devkit/schematics/tasks'; +import { addDependencies, alignFirebaseVersion, pinInstalledPrereleaseVersion } from '../common'; +import { DeployOptions } from '../interfaces'; +import { peerDependencies } from '../versions.json'; + +export const ngAdd = (options: DeployOptions) => (tree: Tree, context: SchematicContext) => { + addDependencies( + tree, + peerDependencies, + context + ); + alignFirebaseVersion(tree, context); + pinInstalledPrereleaseVersion(tree, context); + const npmInstallTaskId = context.addTask(new NodePackageInstallTask()); + context.addTask(new RunSchematicTask('ng-add-setup-project', options), [npmInstallTaskId]); + return tree; +}; diff --git a/src/schematics/add/schema.json b/src/schematics/add/schema.json new file mode 100644 index 000000000..5b1ce2c4f --- /dev/null +++ b/src/schematics/add/schema.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "angular-fire-ng-add", + "title": "AngularFire ng-add", + "type": "object", + "properties": { + "project": { + "type": "string", + "description": "The name of the project.", + "$default": { + "$source": "projectName" + } + } + }, + "required": [] + } diff --git a/src/schematics/builders.json b/src/schematics/builders.json new file mode 100644 index 000000000..0ca9a3307 --- /dev/null +++ b/src/schematics/builders.json @@ -0,0 +1,10 @@ +{ + "$schema": "../../node_modules/@angular-devkit/schematics/collection-schema.json", + "builders": { + "deploy": { + "implementation": "./deploy/builder", + "schema": "./deploy/schema.json", + "description": "Deploy builder" + } + } +} \ No newline at end of file diff --git a/src/schematics/collection.json b/src/schematics/collection.json new file mode 100644 index 000000000..be353c37d --- /dev/null +++ b/src/schematics/collection.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../node_modules/@angular-devkit/schematics/collection-schema.json", + "schematics": { + "ng-add": { + "description": "Add firebase deploy schematic", + "factory": "./add#ngAdd", + "schema": "./add/schema.json" + }, + "ng-add-setup-project": { + "description": "Setup ng deploy", + "factory": "./setup#ngAddSetupProject", + "schema": "./setup/schema.json" + } + } +} diff --git a/src/schematics/common.jasmine.ts b/src/schematics/common.jasmine.ts new file mode 100644 index 000000000..0322ae89d --- /dev/null +++ b/src/schematics/common.jasmine.ts @@ -0,0 +1,226 @@ +import { readFileSync } from 'fs'; +import { logging } from '@angular-devkit/core'; +import { HostTree, SchematicContext } from '@angular-devkit/schematics'; +import { alignFirebaseVersion, firebaseVersionRange, pinInstalledPrereleaseVersion } from './common.js'; +import 'jasmine'; + +const context = { logger: new logging.Logger('test') } as unknown as SchematicContext; + +const treeWithAngularFire = (declaredVersion: string, section = 'dependencies') => { + const tree = new HostTree(); + tree.create('package.json', JSON.stringify({ + name: 'test-app', + [section]: { + '@angular/fire': declaredVersion, + firebase: '^12.4.0', + }, + }, null, 2)); + return tree; +}; + +const sectionIn = (tree: HostTree, section: string) => + JSON.parse(tree.readText('package.json'))[section]; + +const dependenciesIn = (tree: HostTree) => sectionIn(tree, 'dependencies'); + +describe('pinInstalledPrereleaseVersion', () => { + + it('pins a caret prerelease range to the exact installed version', () => { + const tree = treeWithAngularFire('^21.0.0-rc.0'); + pinInstalledPrereleaseVersion(tree, context, '21.0.0-rc.0'); + expect(dependenciesIn(tree)['@angular/fire']).toBe('21.0.0-rc.0'); + }); + + it('pins a tilde prerelease range to the exact installed version', () => { + const tree = treeWithAngularFire('~21.0.0-rc.0'); + pinInstalledPrereleaseVersion(tree, context, '21.0.0-rc.0'); + expect(dependenciesIn(tree)['@angular/fire']).toBe('21.0.0-rc.0'); + }); + + it('leaves other dependencies untouched when pinning', () => { + const tree = treeWithAngularFire('^21.0.0-rc.0'); + pinInstalledPrereleaseVersion(tree, context, '21.0.0-rc.0'); + expect(dependenciesIn(tree).firebase).toBe('^12.4.0'); + }); + + it('leaves a stable caret range untouched', () => { + const tree = treeWithAngularFire('^21.0.0'); + pinInstalledPrereleaseVersion(tree, context, '21.0.0'); + expect(dependenciesIn(tree)['@angular/fire']).toBe('^21.0.0'); + }); + + it('leaves an exact prerelease version untouched', () => { + const tree = treeWithAngularFire('21.0.0-rc.0'); + pinInstalledPrereleaseVersion(tree, context, '21.0.0-rc.0'); + expect(dependenciesIn(tree)['@angular/fire']).toBe('21.0.0-rc.0'); + }); + + it('leaves a hand-authored range expression untouched', () => { + const tree = treeWithAngularFire('>=21.0.0-rc.0'); + pinInstalledPrereleaseVersion(tree, context, '21.0.0-rc.0'); + expect(dependenciesIn(tree)['@angular/fire']).toBe('>=21.0.0-rc.0'); + }); + + it('does not pin when the installed version does not satisfy the declared range', () => { + const tree = treeWithAngularFire('^21.0.0-rc.0'); + pinInstalledPrereleaseVersion(tree, context, '22.0.0-rc.0'); + expect(dependenciesIn(tree)['@angular/fire']).toBe('^21.0.0-rc.0'); + }); + + it('warns and does not pin when the installed version is not valid semver', () => { + const warnSpy = spyOn(context.logger, 'warn'); + const tree = treeWithAngularFire('^21.0.0-rc.0'); + pinInstalledPrereleaseVersion(tree, context, 'ANGULARFIRE2_VERSION'); + expect(warnSpy).toHaveBeenCalled(); + expect(dependenciesIn(tree)['@angular/fire']).toBe('^21.0.0-rc.0'); + }); + + it('does nothing when the @angular/fire entry is not a string', () => { + const tree = new HostTree(); + tree.create('package.json', JSON.stringify({ + name: 'test-app', + dependencies: { '@angular/fire': { version: '^21.0.0-rc.0' } }, + })); + expect(() => pinInstalledPrereleaseVersion(tree, context, '21.0.0-rc.0')).not.toThrow(); + expect(dependenciesIn(tree)['@angular/fire']).toEqual({ version: '^21.0.0-rc.0' }); + }); + + it('does nothing when @angular/fire is not a dependency', () => { + const tree = new HostTree(); + tree.create('package.json', JSON.stringify({ name: 'test-app', dependencies: {} })); + expect(() => pinInstalledPrereleaseVersion(tree, context, '21.0.0-rc.0')).not.toThrow(); + expect(dependenciesIn(tree)['@angular/fire']).toBeUndefined(); + }); + + it('does nothing when package.json is absent', () => { + const tree = new HostTree(); + expect(() => pinInstalledPrereleaseVersion(tree, context, '21.0.0-rc.0')).not.toThrow(); + }); + + it('pins a prerelease range found in devDependencies', () => { + const tree = treeWithAngularFire('^21.0.0-rc.0', 'devDependencies'); + pinInstalledPrereleaseVersion(tree, context, '21.0.0-rc.0'); + expect(sectionIn(tree, 'devDependencies')['@angular/fire']).toBe('21.0.0-rc.0'); + }); + + it('pins the dependencies entry when @angular/fire appears in both sections', () => { + const tree = new HostTree(); + tree.create('package.json', JSON.stringify({ + name: 'test-app', + dependencies: { '@angular/fire': '^21.0.0-rc.0' }, + devDependencies: { '@angular/fire': '^21.0.0-rc.0' }, + }, null, 2)); + pinInstalledPrereleaseVersion(tree, context, '21.0.0-rc.0'); + expect(dependenciesIn(tree)['@angular/fire']).toBe('21.0.0-rc.0'); + expect(sectionIn(tree, 'devDependencies')['@angular/fire']).toBe('^21.0.0-rc.0'); + }); + +}); + +const treeWithFirebase = (firebaseVersion?: string, section = 'dependencies') => { + const tree = new HostTree(); + const packageContents: Record = { name: 'test-app' }; + if (firebaseVersion !== undefined) { + packageContents[section] = { firebase: firebaseVersion }; + } + tree.create('package.json', JSON.stringify(packageContents, null, 2)); + return tree; +}; + +describe('alignFirebaseVersion', () => { + + it('rewrites a previous-major range', () => { + const tree = treeWithFirebase('^11.0.0'); + expect(alignFirebaseVersion(tree, context)).toBeTrue(); + expect(dependenciesIn(tree).firebase).toBe(firebaseVersionRange); + }); + + it('rewrites a same-major range that still allows versions below the required floor', () => { + const tree = treeWithFirebase('^12.0.0'); + expect(alignFirebaseVersion(tree, context)).toBeTrue(); + expect(dependenciesIn(tree).firebase).toBe(firebaseVersionRange); + }); + + it('rewrites a wide range that a stale lockfile can hold below the floor', () => { + const tree = treeWithFirebase('>=11'); + expect(alignFirebaseVersion(tree, context)).toBeTrue(); + expect(dependenciesIn(tree).firebase).toBe(firebaseVersionRange); + }); + + it('leaves a compatible range untouched', () => { + const tree = treeWithFirebase('^12.6.0'); + expect(alignFirebaseVersion(tree, context)).toBeFalse(); + expect(dependenciesIn(tree).firebase).toBe('^12.6.0'); + }); + + it('adds firebase when the workspace has none', () => { + const tree = treeWithFirebase(undefined); + expect(alignFirebaseVersion(tree, context)).toBeTrue(); + expect(dependenciesIn(tree).firebase).toBe(firebaseVersionRange); + }); + + it('aligns firebase in devDependencies without duplicating it into dependencies', () => { + const tree = treeWithFirebase('^11.0.0', 'devDependencies'); + expect(alignFirebaseVersion(tree, context)).toBeTrue(); + expect(sectionIn(tree, 'devDependencies').firebase).toBe(firebaseVersionRange); + expect(sectionIn(tree, 'dependencies')).toBeUndefined(); + }); + + it('warns about a non-semver specifier and leaves it as-is', () => { + const warnSpy = spyOn(context.logger, 'warn'); + const tree = treeWithFirebase('latest'); + expect(alignFirebaseVersion(tree, context)).toBeFalse(); + expect(dependenciesIn(tree).firebase).toBe('latest'); + expect(warnSpy).toHaveBeenCalled(); + }); + + it('warns about a workspace specifier and leaves it as-is', () => { + const warnSpy = spyOn(context.logger, 'warn'); + const tree = treeWithFirebase('workspace:*'); + expect(alignFirebaseVersion(tree, context)).toBeFalse(); + expect(dependenciesIn(tree).firebase).toBe('workspace:*'); + expect(warnSpy).toHaveBeenCalled(); + }); + + it('rewrites stale entries in both sections when firebase appears twice', () => { + const tree = new HostTree(); + tree.create('package.json', JSON.stringify({ + name: 'test-app', + dependencies: { firebase: '^11.0.0' }, + devDependencies: { firebase: '~11.9.0' }, + }, null, 2)); + expect(alignFirebaseVersion(tree, context)).toBeTrue(); + expect(dependenciesIn(tree).firebase).toBe(firebaseVersionRange); + expect(sectionIn(tree, 'devDependencies').firebase).toBe(firebaseVersionRange); + }); + + it('rewrites only the stale section when the other is already compatible', () => { + const tree = new HostTree(); + tree.create('package.json', JSON.stringify({ + name: 'test-app', + dependencies: { firebase: '^12.6.0' }, + devDependencies: { firebase: '^11.0.0' }, + }, null, 2)); + expect(alignFirebaseVersion(tree, context)).toBeTrue(); + expect(dependenciesIn(tree).firebase).toBe('^12.6.0'); + expect(sectionIn(tree, 'devDependencies').firebase).toBe(firebaseVersionRange); + }); + + it('matches the firebase range the library actually ships with', () => { + const libraryManifest = JSON.parse(readFileSync('src/package.json', 'utf8')); + expect(libraryManifest.dependencies.firebase).toBe(firebaseVersionRange); + }); + + it('is a no-op when run a second time', () => { + const tree = treeWithFirebase('^11.0.0'); + expect(alignFirebaseVersion(tree, context)).toBeTrue(); + expect(alignFirebaseVersion(tree, context)).toBeFalse(); + expect(dependenciesIn(tree).firebase).toBe(firebaseVersionRange); + }); + + it('throws when package.json is absent', () => { + const tree = new HostTree(); + expect(() => alignFirebaseVersion(tree, context)).toThrow(); + }); + +}); diff --git a/src/schematics/common.ts b/src/schematics/common.ts new file mode 100644 index 000000000..d34d3c643 --- /dev/null +++ b/src/schematics/common.ts @@ -0,0 +1,188 @@ +import { SchematicContext, SchematicsException, Tree } from '@angular-devkit/schematics'; +import { + intersects as semverIntersects, + prerelease as semverPrerelease, + satisfies as semverSatisfies, + subset as semverSubset, + valid as semverValid, +} from 'semver'; +import { FirebaseHostingSite } from './interfaces'; + +export const shortSiteName = (site?: FirebaseHostingSite) => site?.name?.split('/').pop(); + +export const stringifyFormatted = (obj: any) => JSON.stringify(obj, null, 2); + +export const overwriteIfExists = ( + tree: Tree, + path: string, + content: string +) => { + if (tree.exists(path)) { + tree.overwrite(path, content); + } else { + tree.create(path, content); + } +}; + +export function safeReadJSON(path: string, tree: Tree) { + try { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return JSON.parse(tree.read(path)!.toString()); + } catch (e) { + throw new SchematicsException(`Error when parsing ${path}: ${e.message}`); + } +} + +export const addDependencies = ( + host: Tree, + deps: Record, + context: SchematicContext +) => { + const packageJson = + host.exists('package.json') && safeReadJSON('package.json', host); + + if (packageJson === undefined) { + throw new SchematicsException('Could not locate package.json'); + } + + packageJson.devDependencies ??= {}; + packageJson.dependencies ??= {}; + + Object.keys(deps).forEach(depName => { + const dep = deps[depName]; + const existingDeps = dep.dev ? packageJson.devDependencies : packageJson.dependencies; + const existingVersion = existingDeps[depName]; + if (existingVersion) { + try { + if (!semverIntersects(existingVersion, dep.version)) { + context.logger.warn(`âš ī¸ The ${depName} devDependency specified in your package.json (${existingVersion}) does not fulfill AngularFire's dependency (${dep.version})`); + // TODO offer to fix + } + } catch (_) { + if (existingVersion !== dep.version) { + context.logger.warn(`âš ī¸ The ${depName} devDependency specified in your package.json (${existingVersion}) does not fulfill AngularFire's dependency (${dep.version})`); + // TODO offer to fix + } + } + } else { + existingDeps[depName] = dep.version; + } + }); + + overwriteIfExists(host, 'package.json', stringifyFormatted(packageJson)); +}; + +// Must stay identical to `dependencies.firebase` in `src/package.json`: if the two drift, the +// alignment below can pin workspaces outside the range the library actually installs against, +// re-creating the duplicate-SDK trees it exists to prevent. +export const firebaseVersionRange = '^12.4.0'; + +/** + * Aligns the workspace's `firebase` entry with the range `@angular/fire` requires. + * + * `@angular/fire` bundles its own `firebase` dependency, so a workspace pinned to an older + * major never conflicts at install time; npm silently nests a second SDK copy under + * `@angular/fire`, and the two copies reject each other's objects at runtime (#3684, #3681, + * #3682). Rewriting the workspace's range before the install runs is the only point where + * that class of breakage can be headed off. + * + * Returns whether `package.json` was modified, so callers can skip scheduling an install + * when nothing changed (`ng update` re-runs the v21 migration on rc-to-stable updates). + */ +export const alignFirebaseVersion = ( + host: Tree, + context: SchematicContext, +): boolean => { + if (!host.exists('package.json')) { + throw new SchematicsException('Could not locate package.json'); + } + const packageJson = safeReadJSON('package.json', host); + + const sectionsWithFirebase = ['dependencies', 'devDependencies'].filter( + section => typeof packageJson[section]?.firebase === 'string', + ); + + if (sectionsWithFirebase.length === 0) { + packageJson.dependencies ??= {}; + packageJson.dependencies.firebase = firebaseVersionRange; + context.logger.info(`Added firebase ${firebaseVersionRange} to your package.json.`); + overwriteIfExists(host, 'package.json', stringifyFormatted(packageJson)); + return true; + } + + let changed = false; + for (const section of sectionsWithFirebase) { + const declaredFirebaseVersion = packageJson[section].firebase; + let alreadyCompatible: boolean; + try { + alreadyCompatible = semverSubset(declaredFirebaseVersion, firebaseVersionRange); + } catch (_) { + context.logger.warn( + `âš ī¸ The firebase version in your package.json (${declaredFirebaseVersion}) is not a semver ` + + `range, so it was left as-is; make sure it resolves inside ${firebaseVersionRange}, the ` + + 'range @angular/fire requires; a version outside it can leave the install with two copies of the firebase SDK.' + ); + continue; + } + if (alreadyCompatible) { continue; } + packageJson[section].firebase = firebaseVersionRange; + context.logger.info( + `Updated the firebase version in your package.json from ${declaredFirebaseVersion} to ` + + `${firebaseVersionRange}, the range @angular/fire requires; a workspace range outside it ` + + 'can leave the install with a second copy of the firebase SDK, which fails at runtime.' + ); + changed = true; + } + + if (changed) { + overwriteIfExists(host, 'package.json', stringifyFormatted(packageJson)); + } + return changed; +}; + +// The build writes the published version over this placeholder in the compiled schematics +// (tools/build.ts, replaceSchematicVersions); running from source leaves the placeholder. +const angularFireVersion = 'ANGULARFIRE2_VERSION'; + +/** + * Pins the workspace's `@angular/fire` entry to the exact installed version when `ng add` wrote a + * prerelease range. A prerelease range like `^21.0.0-rc.0` also matches the canary build published + * for every merge to main, so a later fresh install can silently replace the version the user + * chose. Stable ranges are left untouched. + */ +export const pinInstalledPrereleaseVersion = ( + host: Tree, + context: SchematicContext, + installedVersion = angularFireVersion, +) => { + if (!host.exists('package.json')) { return; } + const packageJson = safeReadJSON('package.json', host); + + const dependencySection = ['dependencies', 'devDependencies'].find( + section => typeof packageJson[section]?.['@angular/fire'] === 'string', + ); + if (!dependencySection) { return; } + + const declaredAngularFireVersion = packageJson[dependencySection]['@angular/fire']; + if (!(declaredAngularFireVersion.startsWith('^') || declaredAngularFireVersion.startsWith('~'))) { return; } + + if (!semverValid(installedVersion)) { + context.logger.warn( + 'Could not determine the installed @angular/fire version; leaving the declared version range as-is.' + ); + return; + } + + if ( + semverPrerelease(installedVersion) && + semverSatisfies(installedVersion, declaredAngularFireVersion, { includePrerelease: true }) + ) { + packageJson[dependencySection]['@angular/fire'] = installedVersion; + overwriteIfExists(host, 'package.json', stringifyFormatted(packageJson)); + context.logger.info( + `Pinned @angular/fire to the exact version ${installedVersion} — a prerelease range like ` + + `${declaredAngularFireVersion} also matches unreviewed canary builds, so a later install ` + + 'could silently change versions.' + ); + } +}; diff --git a/src/schematics/deploy/actions.jasmine.ts b/src/schematics/deploy/actions.jasmine.ts index c723f07ab..f3ecdd600 100644 --- a/src/schematics/deploy/actions.jasmine.ts +++ b/src/schematics/deploy/actions.jasmine.ts @@ -1,7 +1,10 @@ -import { experimental, JsonObject, logging } from '@angular-devkit/core'; +/* eslint-disable @typescript-eslint/no-empty-function */ +import { join } from 'path'; +import { Script } from 'vm'; import { BuilderContext, BuilderRun, ScheduleOptions, Target } from '@angular-devkit/architect'; -import { BuildTarget, FirebaseDeployConfig, FirebaseTools, FSHost } from '../interfaces'; -import deploy, { deployToFunction } from './actions'; +import { JsonObject, logging } from '@angular-devkit/core'; +import { BuildTarget, DeployBuilderSchema, FSHost, FirebaseDeployConfig, FirebaseTools } from '../interfaces'; +import deploy, { assertSafeDependencyName, assertSafeFunctionName, assertSafeNodeVersion, assertSafeOutputPath, assertSupportedPackageManager, buildCloudRunBuildsSubmitArgs, buildCloudRunDeployArgs, deployToCloudRun, deployToFunction, findPackageVersion, processHost } from './actions.js' import 'jasmine'; let context: BuilderContext; @@ -10,10 +13,23 @@ let fsHost: FSHost; const FIREBASE_PROJECT = 'ikachu-aa3ef'; const PROJECT = 'pirojok-project'; -const BUILD_TARGET: BuildTarget = { +const STATIC_BUILD_TARGET: BuildTarget = { name: `${PROJECT}:build:production` }; +const FIREBASE_TOKEN = 'kkasllkascnkjnskjsdcskdckskdksdkjc'; + +const SERVER_BUILD_TARGET: BuildTarget = { + name: `${PROJECT}:server:production` +}; + +const login = () => Promise.resolve({ user: { email: 'foo@bar.baz' }}); +login.list = () => Promise.resolve([{ user: { email: 'foo@bar.baz' }}]); +login.add = () => Promise.resolve([{ user: { email: 'foo@bar.baz' }}]); +login.use = () => Promise.resolve('foo@bar.baz'); + +const workspaceRoot = join('home', 'user'); + const initMocks = () => { fsHost = { moveSync(_: string, __: string) { @@ -21,20 +37,45 @@ const initMocks = () => { renameSync(_: string, __: string) { }, writeFileSync(_: string, __: string) { - } + }, + copySync(_: string, __: string) { + }, + removeSync(_: string) { + }, + existsSync(_: string) { + return false; + }, }; firebaseMock = { - login: () => Promise.resolve(), + login, projects: { - list: () => Promise.resolve([]) + list: () => Promise.resolve([]), + create: () => Promise.reject(), + }, + apps: { + list: () => Promise.resolve([]), + create: () => Promise.reject(), + sdkconfig: () => Promise.resolve({ fileName: '_', fileContents: '', sdkConfig: {}, }), + }, + hosting: { + sites: { + list: () => Promise.resolve({sites: []}), + create: () => Promise.reject(), + } + }, + init() { + return Promise.resolve() }, deploy: (_: FirebaseDeployConfig) => Promise.resolve(), use: () => Promise.resolve(), logger: { - add: () => { + add: () => { }, + logger: { + add: () => { } } }, + cli: { version: () => '9.0.0' }, serve: () => Promise.resolve() }; @@ -53,7 +94,13 @@ const initMocks = () => { id: 1, logger: new logging.NullLogger() as any, workspaceRoot: 'cwd', - getTargetOptions: (_: Target) => Promise.resolve({}), + getTargetOptions: (target: Target) => { + if (target.target === 'build') { + return { outputPath: 'dist/browser' }; + } else if (target.target === 'server') { + return { outputPath: 'dist/server' }; + } + }, reportProgress: (_: number, __?: number, ___?: string) => { }, reportStatus: (_: string) => { @@ -65,32 +112,27 @@ const initMocks = () => { } as any); }; - -const projectTargets: experimental.workspace.WorkspaceTool = { - build: { - options: { - outputPath: 'dist/browser' - } - }, - server: { - options: { - outputPath: 'dist/server' - } - } -}; - describe('Deploy Angular apps', () => { beforeEach(() => initMocks()); it('should call login', async () => { - const spy = spyOn(firebaseMock, 'login'); - await deploy(firebaseMock, context, projectTargets, [BUILD_TARGET], FIREBASE_PROJECT, false, false); + const spy = spyOn(firebaseMock, 'login').and.resolveTo({ email: 'foo@bar.baz' }); + await deploy( + firebaseMock, context, STATIC_BUILD_TARGET, undefined, + undefined, undefined, { projectId: FIREBASE_PROJECT, preview: false } + ); expect(spy).toHaveBeenCalled(); }); + it('should not call login', async () => { + const spy = spyOn(firebaseMock, 'login'); + await deploy(firebaseMock, context, STATIC_BUILD_TARGET, undefined, undefined, undefined, { preview: false }, FIREBASE_TOKEN); + expect(spy).not.toHaveBeenCalled(); + }); + it('should invoke the builder', async () => { const spy = spyOn(context, 'scheduleTarget').and.callThrough(); - await deploy(firebaseMock, context, projectTargets, [BUILD_TARGET], FIREBASE_PROJECT, false, false); + await deploy(firebaseMock, context, STATIC_BUILD_TARGET, undefined, undefined, undefined, { preview: false }); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith({ target: 'build', @@ -105,27 +147,29 @@ describe('Deploy Angular apps', () => { options: {} }; const spy = spyOn(context, 'scheduleTarget').and.callThrough(); - await deploy(firebaseMock, context, projectTargets, [buildTarget], FIREBASE_PROJECT, false, false); + await deploy(firebaseMock, context, buildTarget, undefined, undefined, undefined, { preview: false }); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith({ target: 'prerender', project: PROJECT }, {}); }); it('should invoke firebase.deploy', async () => { const spy = spyOn(firebaseMock, 'deploy').and.callThrough(); - await deploy(firebaseMock, context, projectTargets, [BUILD_TARGET], FIREBASE_PROJECT, false, false); + await deploy(firebaseMock, context, STATIC_BUILD_TARGET, undefined, undefined, undefined, { preview: false }, FIREBASE_TOKEN); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith({ cwd: 'cwd', - only: 'hosting:' + PROJECT + only: 'hosting:' + PROJECT, + token: FIREBASE_TOKEN, + nonInteractive: true, + projectRoot: 'cwd', }); }); describe('error handling', () => { it('throws if there is no firebase project', async () => { try { - await deploy(firebaseMock, context, projectTargets, [BUILD_TARGET], undefined, false, false); + await deploy(firebaseMock, context, STATIC_BUILD_TARGET, undefined, undefined, undefined, { preview: false }); } catch (e) { - console.log(e); expect(e.message).toMatch(/Cannot find firebase project/); } }); @@ -133,7 +177,7 @@ describe('Deploy Angular apps', () => { it('throws if there is no target project', async () => { context.target = undefined; try { - await deploy(firebaseMock, context, projectTargets, [BUILD_TARGET], FIREBASE_PROJECT, false, false); + await deploy(firebaseMock, context, STATIC_BUILD_TARGET, undefined, undefined, undefined, { preview: false }); } catch (e) { expect(e.message).toMatch(/Cannot execute the build target/); } @@ -146,34 +190,106 @@ describe('universal deployment', () => { it('should create a firebase function', async () => { const spy = spyOn(fsHost, 'writeFileSync'); - await deployToFunction(firebaseMock, context, '/home/user', projectTargets, false, fsHost); + await deployToFunction( + firebaseMock, + context, + workspaceRoot, + STATIC_BUILD_TARGET, + SERVER_BUILD_TARGET, + { preview: false }, + undefined, + fsHost + ); expect(spy).toHaveBeenCalledTimes(2); const packageArgs = spy.calls.argsFor(0); const functionArgs = spy.calls.argsFor(1); - expect(packageArgs[0]).toBe('dist/package.json'); - expect(functionArgs[0]).toBe('dist/index.js'); + expect(packageArgs[0]).toBe(join(workspaceRoot, 'dist', 'package.json')); + expect(functionArgs[0]).toBe(join(workspaceRoot, 'dist', 'index.js')); + }); + + it('should create a firebase function (new)', async () => { + const spy = spyOn(fsHost, 'writeFileSync'); + await deployToFunction( + firebaseMock, + context, + workspaceRoot, + STATIC_BUILD_TARGET, + SERVER_BUILD_TARGET, + { preview: false, outputPath: join('dist', 'functions') }, + undefined, + fsHost + ); + + expect(spy).toHaveBeenCalledTimes(2); + + const packageArgs = spy.calls.argsFor(0); + const functionArgs = spy.calls.argsFor(1); + + expect(packageArgs[0]).toBe(join(workspaceRoot, 'dist', 'functions', 'package.json')); + expect(functionArgs[0]).toBe(join(workspaceRoot, 'dist', 'functions', 'index.js')); }); it('should rename the index.html file in the nested dist', async () => { const spy = spyOn(fsHost, 'renameSync'); - await deployToFunction(firebaseMock, context, '/home/user', projectTargets, false, fsHost); + await deployToFunction( + firebaseMock, + context, + workspaceRoot, + STATIC_BUILD_TARGET, + SERVER_BUILD_TARGET, + { preview: false }, + undefined, + fsHost + ); expect(spy).toHaveBeenCalledTimes(1); const packageArgs = spy.calls.argsFor(0); expect(packageArgs).toEqual([ - 'dist/dist/browser/index.html', - 'dist/dist/browser/index.original.html' + join(workspaceRoot, 'dist', 'dist', 'browser', 'index.html'), + join(workspaceRoot, 'dist', 'dist', 'browser', 'index.original.html') + ]); + }); + + it('should rename the index.html file in the nested dist (new)', async () => { + const spy = spyOn(fsHost, 'renameSync'); + await deployToFunction( + firebaseMock, + context, + workspaceRoot, + STATIC_BUILD_TARGET, + SERVER_BUILD_TARGET, + { preview: false, outputPath: join('dist', 'functions') }, + undefined, + fsHost + ); + + expect(spy).toHaveBeenCalledTimes(1); + + const packageArgs = spy.calls.argsFor(0); + + expect(packageArgs).toEqual([ + join(workspaceRoot, 'dist', 'functions', 'dist', 'browser', 'index.html'), + join(workspaceRoot, 'dist', 'functions', 'dist', 'browser', 'index.original.html') ]); }); it('should invoke firebase.deploy', async () => { const spy = spyOn(firebaseMock, 'deploy'); - await deployToFunction(firebaseMock, context, '/home/user', projectTargets, false, fsHost); + await deployToFunction( + firebaseMock, + context, + workspaceRoot, + STATIC_BUILD_TARGET, + SERVER_BUILD_TARGET, + { preview: false }, + undefined, + fsHost + ); expect(spy).toHaveBeenCalledTimes(1); }); @@ -185,3 +301,334 @@ describe('universal deployment', () => { expect(spy).not.toHaveBeenCalled(); });*/ }); + +describe('Cloud Run gcloud argv construction', () => { + // Regression coverage for the argv-injection fix: these options used to be interpolated + // into a single command string and split on whitespace, so a value containing a space + // would land as extra, unintended argv entries. They're now passed straight through as + // individual array elements. + const INJECTED_REGION = 'us-central1 --set-env-vars=INJECTED=owned'; + const INJECTED_PROJECT = `${FIREBASE_PROJECT} --format=json`; + + it('keeps a region value containing a space as a single --region argument', () => { + const options: DeployBuilderSchema = { firebaseProject: FIREBASE_PROJECT, region: INJECTED_REGION }; + const args = buildCloudRunDeployArgs('my-service', options, []); + + expect(args[args.indexOf('--region') + 1]).toBe(INJECTED_REGION); + expect(args).not.toContain('--set-env-vars=INJECTED=owned'); + }); + + it('keeps a firebaseProject value containing a space as a single --project argument (deploy)', () => { + const options: DeployBuilderSchema = { firebaseProject: INJECTED_PROJECT, region: 'us-central1' }; + const args = buildCloudRunDeployArgs('my-service', options, []); + + expect(args[args.indexOf('--project') + 1]).toBe(INJECTED_PROJECT); + expect(args).not.toContain('--format=json'); + }); + + it('keeps a firebaseProject value containing a space as a single --project argument (builds submit)', () => { + const options: DeployBuilderSchema = { firebaseProject: INJECTED_PROJECT }; + const args = buildCloudRunBuildsSubmitArgs('cloudRunOut', 'my-service', options); + + expect(args[args.indexOf('--project') + 1]).toBe(INJECTED_PROJECT); + expect(args).not.toContain('--format=json'); + }); + + it('passes cloudRunOptions through as their own argv entries', () => { + const options: DeployBuilderSchema = { firebaseProject: FIREBASE_PROJECT, region: 'us-central1' }; + const args = buildCloudRunDeployArgs('my-service', options, ['--vpc-connector', 'my-connector --unset-env-vars=OWNED']); + + expect(args[args.indexOf('--vpc-connector') + 1]).toBe('my-connector --unset-env-vars=OWNED'); + expect(args).not.toContain('--unset-env-vars=OWNED'); + }); +}); + +describe('deploy input validation (command-injection hardening)', () => { + describe('assertSupportedPackageManager', () => { + ['npm', 'yarn', 'pnpm', 'cnpm', 'bun'].forEach((pm) => { + it(`allows the supported package manager "${pm}"`, () => { + expect(assertSupportedPackageManager(pm)).toBe(pm); + }); + }); + + it('rejects a package manager carrying a shell payload', () => { + expect(() => assertSupportedPackageManager('npm; touch /tmp/pwned #')) + .toThrowError(/Unsupported package manager/); + }); + + it('rejects an arbitrary executable path', () => { + expect(() => assertSupportedPackageManager('/tmp/evil')).toThrowError(/Unsupported package manager/); + }); + }); + + describe('assertSafeDependencyName', () => { + ['rxjs', '@angular/core', '@angular/*', 'some-pkg', 'a.b_c'].forEach((name) => { + it(`allows the valid dependency name "${name}"`, () => { + expect(assertSafeDependencyName(name)).toBe(name); + }); + }); + + ['evil; touch /tmp/pwned #', 'a b', '$(id)', '`id`', 'a|b', 'a&b', '-rf', '', 'a>b'].forEach((name) => { + it(`rejects the unsafe dependency name ${JSON.stringify(name)}`, () => { + expect(() => assertSafeDependencyName(name)).toThrowError(/Invalid dependency name/); + }); + }); + }); + + // These guard the fix at its call sites: the validators above are only useful + // if the deploy code keeps routing every command through the shell-free runner. + // A regression to execSync/`shell: true`, or a dropped validator call, fails here. + describe('call sites route through the shell-free runner', () => { + beforeEach(() => initMocks()); + + it('installs functions dependencies via the runner with an argv array and no shell', async () => { + // The install branch only runs when the generated package.json exists. + spyOn(fsHost, 'existsSync').and.returnValue(true); + const runSpy = spyOn(processHost, 'runPackageBin').and.returnValue(Buffer.from('')); + + await deployToFunction( + firebaseMock, + context, + workspaceRoot, + STATIC_BUILD_TARGET, + SERVER_BUILD_TARGET, + { preview: false }, + undefined, + fsHost + ); + + expect(runSpy).toHaveBeenCalledTimes(1); + const [command, args, options] = runSpy.calls.mostRecent().args; + expect(command).toBe('npm'); + expect(args).toEqual(['--prefix', join(workspaceRoot, 'dist'), 'install']); + // No shell: a `shell` option would reopen the injection this PR closes. + expect((options as any)?.shell).toBeFalsy(); + }); + + it('runs the package manager through the runner with a validated argv array', () => { + const runSpy = spyOn(processHost, 'runPackageBin').and.returnValue(Buffer.from('')); + + findPackageVersion('npm', 'rxjs'); + + expect(runSpy).toHaveBeenCalledWith('npm', ['list', 'rxjs']); + }); + + it('rejects an unsupported package manager before spawning anything', () => { + const runSpy = spyOn(processHost, 'runPackageBin'); + + expect(() => findPackageVersion('npm; touch /tmp/pwned #', 'rxjs')) + .toThrowError(/Unsupported package manager/); + expect(runSpy).not.toHaveBeenCalled(); + }); + + it('rejects an unsafe dependency name before spawning anything', () => { + const runSpy = spyOn(processHost, 'runPackageBin'); + + expect(() => findPackageVersion('npm', 'evil; touch /tmp/pwned #')) + .toThrowError(/Invalid dependency name/); + expect(runSpy).not.toHaveBeenCalled(); + }); + }); +}); + +describe('generated artifact validation (codegen-injection hardening)', () => { + describe('assertSafeOutputPath', () => { + [ + 'dist/browser', 'dist/server', 'dist/my-app/browser', 'out', 'a.b-c_d/e', '../dist/browser', + // Only a shell would act on these, and the one place the path reaches a shell is the + // generated start script, which quotes it. So they are unusual directory names rather + // than a way through, and rejecting them would break a deploy that works today. + 'dist/my app', 'dist/*', 'dist/?pp', 'dist/[ab]', '~/x', 'dist\tserver', 'a;b', 'a|b', + ].forEach((outputPath) => { + it(`allows the valid outputPath ${JSON.stringify(outputPath)}`, () => { + expect(() => assertSafeOutputPath(outputPath, 'proj:server')).not.toThrow(); + }); + }); + + [ + `x'); require('child_process').execSync('id'); ('`, + 'a`id`', 'a$(id)', 'a$HOME', 'a\nb', 'a\rb', 'a"b', 'a\\b', '-rf', + ].forEach((outputPath) => { + it(`rejects the unsafe outputPath ${JSON.stringify(outputPath)}`, () => { + expect(() => assertSafeOutputPath(outputPath, 'proj:server')).toThrowError(/Unsafe outputPath/); + }); + }); + }); + + describe('assertSafeNodeVersion', () => { + // A Docker tag, since the Cloud Run path renders it as `FROM node:-slim`. + [undefined, 18, 20, '18', '18.19', '20.11.1', 'lts', 'current', 'iron', '22-bookworm'].forEach((version) => { + it(`allows the valid functionsNodeVersion ${JSON.stringify(version)}`, () => { + expect(() => assertSafeNodeVersion(version)).not.toThrow(); + }); + }); + + [ + '18-slim\nRUN curl evil | sh', '18 && id', '18;id', '$(id)', '`id`', '18/../x', 'x:y', 'x@sha256', + // Grammatical, but node:latest-slim has never been published. + 'latest', + // Docker caps a tag at 128 characters. + '2'.repeat(129), + ].forEach((version) => { + it(`rejects the unsafe functionsNodeVersion ${JSON.stringify(version)}`, () => { + expect(() => assertSafeNodeVersion(version)).toThrowError(/Unsafe functionsNodeVersion/); + }); + }); + }); + + describe('assertSafeFunctionName', () => { + // These are the names that can actually arrive: the schema pattern for functionName is + // the wider Cloud Run service-ID rule, and this is the JavaScript-identifier rule the + // Cloud Functions path needs on top of it. + [undefined, 'ssr', 'ssrHandler', 'a1', 'my_fn'].forEach((functionName) => { + it(`allows the valid functionName ${JSON.stringify(functionName)}`, () => { + expect(() => assertSafeFunctionName(functionName)).not.toThrow(); + }); + }); + + [`ssr; require('child_process').execSync('id'); var _x`, 'my-fn', 'a b', '1fn', 'a.b', `a'`].forEach((functionName) => { + it(`rejects the unsafe functionName ${JSON.stringify(functionName)}`, () => { + expect(() => assertSafeFunctionName(functionName)).toThrowError(/Unsafe functionName/); + }); + }); + }); +}); + +// Runs a generated index.js against stubs, recording what it required and what it ran, so a +// payload that escaped its context is caught by having executed rather than by how it reads. +const runGeneratedFunction = (source: string) => { + const required: string[] = []; + const executed: string[] = []; + const stub: Record = { + app: () => ({}), + https: { onRequest: (app: unknown) => app }, + execSync: (command: string) => { executed.push(command); return ''; }, + }; + stub.region = () => stub; + stub.runWith = () => stub; + const exports: Record = {}; + const run = () => new Script(source).runInNewContext({ + exports, + module: { exports }, + require: (id: string) => { required.push(id); return stub; }, + }); + return { required, executed, exports, run }; +}; + +// These drive the builders end-to-end so the protection cannot be silently dropped: every +// assertSafe* call site in deployToFunction / deployToCloudRun is covered by a spec here +// that fails if that call is removed, and so is the region escaping in the template. That +// includes the static build target, whose outputPath only ever reaches the filesystem and +// so has nothing exploitable to assert beyond the rejection itself. +describe('generated artifact validation is wired into the builders', () => { + beforeEach(() => initMocks()); + + const withOutputPaths = ( + staticOutputPath: string, + serverOutputPath: string, + ): BuilderContext['getTargetOptions'] => (target: Target) => { + if (target.target === 'build') { return Promise.resolve({ outputPath: staticOutputPath }); } + if (target.target === 'server') { return Promise.resolve({ outputPath: serverOutputPath }); } + // Matches architect, which throws rather than handing back options-less targets. + throw new Error(`Invalid target: ${JSON.stringify(target)}.`); + }; + + const withServerOutputPath = (outputPath: string) => withOutputPaths('dist/browser', outputPath); + const withStaticOutputPath = (outputPath: string) => withOutputPaths(outputPath, 'dist/server'); + + const EVIL_PATH = `dist'); require('child_process').execSync('id'); ('`; + + it('deployToFunction rejects a hostile server outputPath', async () => { + context.getTargetOptions = withServerOutputPath(EVIL_PATH); + await expectAsync(deployToFunction( + firebaseMock, context, workspaceRoot, STATIC_BUILD_TARGET, SERVER_BUILD_TARGET, + { preview: false }, undefined, fsHost + )).toBeRejectedWithError(/Unsafe outputPath/); + }); + + it('deployToFunction rejects a hostile static outputPath', async () => { + context.getTargetOptions = withStaticOutputPath(EVIL_PATH); + await expectAsync(deployToFunction( + firebaseMock, context, workspaceRoot, STATIC_BUILD_TARGET, SERVER_BUILD_TARGET, + { preview: false }, undefined, fsHost + )).toBeRejectedWithError(/Unsafe outputPath/); + }); + + it('deployToFunction rejects a server outputPath that starts with a dash', async () => { + context.getTargetOptions = withServerOutputPath('-rf'); + await expectAsync(deployToFunction( + firebaseMock, context, workspaceRoot, STATIC_BUILD_TARGET, SERVER_BUILD_TARGET, + { preview: false }, undefined, fsHost + )).toBeRejectedWithError(/Unsafe outputPath/); + }); + + it('deployToFunction rejects a functionName that is not a plain identifier', async () => { + await expectAsync(deployToFunction( + firebaseMock, context, workspaceRoot, STATIC_BUILD_TARGET, SERVER_BUILD_TARGET, + { preview: false, functionName: `ssr; require('child_process').execSync('id'); var _x` }, + undefined, fsHost + )).toBeRejectedWithError(/Unsafe functionName/); + }); + + it('deployToFunction escapes region into the generated function instead of interpolating it raw', async () => { + const spy = spyOn(fsHost, 'writeFileSync'); + const region = `us-central1'); require('child_process').execSync('id'); ('`; + await deployToFunction( + firebaseMock, context, workspaceRoot, STATIC_BUILD_TARGET, SERVER_BUILD_TARGET, + { preview: false, region }, undefined, fsHost + ); + // By path rather than by call order, so adding or reordering a write does not silently + // point this at the wrong file. + const write = spy.calls.allArgs().find(([path]) => path.endsWith('index.js')); + if (!write) { throw new Error('deployToFunction wrote no index.js'); } + const indexJs = write[1]; + expect(indexJs).toContain(`.region(${JSON.stringify(region)})`); + + // Interpolated raw, the payload closes `.region('` and the require becomes a statement + // of its own, which runs when the function loads. Rendered through the fixed template it + // stays inside a string literal, so running the source touches neither. + const generated = runGeneratedFunction(indexJs); + expect(generated.run).not.toThrow(); + expect(generated.executed).toEqual([]); + expect(generated.required).not.toContain('child_process'); + expect(Object.keys(generated.exports)).toEqual(['ssr']); + }); + + it('deployToCloudRun rejects a hostile server outputPath', async () => { + context.getTargetOptions = withServerOutputPath(EVIL_PATH); + await expectAsync(deployToCloudRun( + firebaseMock, context, workspaceRoot, STATIC_BUILD_TARGET, SERVER_BUILD_TARGET, + { preview: false }, undefined, fsHost + )).toBeRejectedWithError(/Unsafe outputPath/); + }); + + it('deployToCloudRun rejects a hostile static outputPath', async () => { + context.getTargetOptions = withStaticOutputPath(EVIL_PATH); + await expectAsync(deployToCloudRun( + firebaseMock, context, workspaceRoot, STATIC_BUILD_TARGET, SERVER_BUILD_TARGET, + { preview: false }, undefined, fsHost + )).toBeRejectedWithError(/Unsafe outputPath/); + }); + + it('deployToCloudRun rejects a hostile functionsNodeVersion', async () => { + await expectAsync(deployToCloudRun( + firebaseMock, context, workspaceRoot, STATIC_BUILD_TARGET, SERVER_BUILD_TARGET, + { preview: false, functionsNodeVersion: '18-slim\nRUN curl evil | sh' }, undefined, fsHost + )).toBeRejectedWithError(/Unsafe functionsNodeVersion/); + }); + + it('deployToCloudRun rejects a hostile functionsNodeVersion before touching the output directory', async () => { + const removeSpy = spyOn(fsHost, 'removeSync'); + const copySpy = spyOn(fsHost, 'copySync'); + const writeSpy = spyOn(fsHost, 'writeFileSync'); + + await expectAsync(deployToCloudRun( + firebaseMock, context, workspaceRoot, STATIC_BUILD_TARGET, SERVER_BUILD_TARGET, + { preview: false, functionsNodeVersion: '18-slim\nRUN curl evil | sh' }, undefined, fsHost + )).toBeRejectedWithError(/Unsafe functionsNodeVersion/); + + expect(removeSpy).not.toHaveBeenCalled(); + expect(copySpy).not.toHaveBeenCalled(); + expect(writeSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/src/schematics/deploy/actions.ts b/src/schematics/deploy/actions.ts index 0a7bab9bf..b3f71e86a 100644 --- a/src/schematics/deploy/actions.ts +++ b/src/schematics/deploy/actions.ts @@ -1,278 +1,682 @@ -import { BuilderContext, targetFromTargetString } from '@angular-devkit/architect'; -import { BuildTarget, FirebaseTools, FSHost } from '../interfaces'; +import { SpawnOptionsWithoutStdio, SpawnSyncOptions, spawn } from 'child_process'; import { existsSync, readFileSync, renameSync, writeFileSync } from 'fs'; -import { copySync, removeSync } from 'fs-extra'; import { dirname, join } from 'path'; -import { execSync } from 'child_process'; -import { defaultFunction, defaultPackage, NODE_VERSION } from './functions-templates'; -import { experimental } from '@angular-devkit/core'; +import { fileURLToPath } from 'url'; +import { BuilderContext, targetFromTargetString } from '@angular-devkit/architect'; import { SchematicsException } from '@angular-devkit/schematics'; +import crossSpawn from 'cross-spawn'; +import fsExtra from 'fs-extra'; +import * as inquirer from 'inquirer'; +import open from 'open'; import { satisfies } from 'semver'; -import * as open from 'open'; +import tripleBeam from 'triple-beam'; +import * as winston from 'winston'; +import { BuildTarget, CloudRunOptions, DeployBuilderSchema, FSHost, FirebaseTools } from '../interfaces'; +import { DEFAULT_FUNCTION_NAME, defaultFunction, defaultPackage, dockerfile, functionGen2 } from './functions-templates.js'; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore `import.meta` is rejected by the --module es2015 pass of `npm run build:jasmine`. +const moduleDirectory = typeof __dirname === 'string' ? __dirname : dirname(fileURLToPath(import.meta.url)); + +const { copySync, removeSync, readJsonSync } = fsExtra; + +const DEFAULT_EMULATOR_PORT = 5000; +const DEFAULT_EMULATOR_HOST = 'localhost'; + +const DEFAULT_CLOUD_RUN_OPTIONS: Partial = { + memory: '1Gi', + timeout: 60, + maxInstances: 'default', + maxConcurrency: 'default', // TODO tune concurrency for cloud run + angular + minInstances: 'default', + cpus: 1, +}; + +const spawnAsync = async ( + command: string, + args: string[], + options?: SpawnOptionsWithoutStdio +) => + new Promise((resolve, reject) => { + const spawnProcess = spawn(command, args, options); + const chunks: Buffer[] = []; + const errorChunks: Buffer[] = []; + spawnProcess.stdout.on('data', (data) => { + process.stdout.write(data.toString()); + chunks.push(data); + }); + spawnProcess.stderr.on('data', (data) => { + process.stderr.write(data.toString()); + errorChunks.push(data); + }); + spawnProcess.on('error', (error) => { + reject(error); + }); + spawnProcess.on('close', (code) => { + if (code !== 0) { + reject(Buffer.concat(errorChunks).toString()); + return; + } + resolve(Buffer.concat(chunks)); + }); + }); + +export type DeployBuilderOptions = DeployBuilderSchema & Record; + +const escapeRegExp = (str: string) => str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&'); + +// A build target's outputPath (from angular.json's architect...options) +// is interpolated raw into generated Cloud Function source (`require('.//main')`) +// and into the generated package.json start script, which the Cloud Run image runs through +// a shell. That start script quotes the path (functions-templates.ts), so word splitting, +// globbing and `~` expansion are already off; what remains live is the set below. +// +// ' and \ break out of the require() string literal, as do the line terminators, since a +// JavaScript string literal cannot span a line +// " ` and $ stay live inside the double quotes of the start script: `"` closes them, and +// `` ` `` and `$(` are still command substitution in there +// +// A leading dash is rejected separately: quoting does not stop `node "-rf/main.js"` from +// being read as a flag rather than a path. +export const assertSafeOutputPath = (outputPath: string, targetName: string): void => { + if (/['"`\\$\n\r]/.test(outputPath) || outputPath.startsWith('-')) { + throw new SchematicsException( + `Unsafe outputPath ${JSON.stringify(outputPath)} for target '${targetName}' in angular.json.` + ); + } +}; + +// functionName is interpolated raw into the generated Cloud Function source as the +// `exports.` assignment target (functions-templates.ts), which is executed when the +// function loads. Allow only a plain JavaScript identifier so it cannot introduce further +// statements; this also turns a name that would silently produce an unparseable file (for +// example one containing a dash) into an explicit error. +export const assertSafeFunctionName = (functionName: string | undefined): void => { + if (functionName !== undefined && !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(functionName)) { + throw new SchematicsException( + `Unsafe functionName ${JSON.stringify(functionName)} in angular.json; expected a plain identifier.` + ); + } +}; -const escapeRegExp = (str: string) => str.replace(/[\-\[\]\/{}()*+?.\\^$|]/g, '\\$&'); +// functionsNodeVersion is interpolated raw into the generated Dockerfile's FROM line +// (`FROM node:-slim`), executed during the Cloud Run container build, so the value +// is a Docker image tag. This is Docker's own tag grammar, which bounds the length at 128 +// and admits none of the characters that would open a new instruction (a line terminator) +// or point FROM at different image content (a space, a slash, a colon or an `@`). `latest` +// is grammatical but has never resolved here: the official image publishes its slim variant +// as node:slim, so node:latest-slim does not exist. +// Kept in step with the functionsNodeVersion pattern in schema.json. +const NODE_IMAGE_TAG = /^(?!latest$)[\w][\w.-]{0,127}$/; + +export const assertSafeNodeVersion = (version: string | number | undefined): void => { + if (version !== undefined && !NODE_IMAGE_TAG.test(String(version))) { + throw new SchematicsException( + `Unsafe functionsNodeVersion ${JSON.stringify(version)} in angular.json; expected a node image tag, such as 22 or lts.` + ); + } +}; const moveSync = (src: string, dest: string) => { copySync(src, dest); removeSync(src); }; -const deployToHosting = ( +const deployToHosting = async ( firebaseTools: FirebaseTools, context: BuilderContext, workspaceRoot: string, - preview: boolean + options: DeployBuilderOptions, + firebaseToken?: string, ) => { - if (preview) { - const port = 5000; // TODO make this configurable - - setTimeout(() => { - open(`http://localhost:${port}`); - }, 1500); - - return firebaseTools.serve({ port, targets: ['hosting'], host: 'localhost' }).then(() => - require('inquirer').prompt({ - type: 'confirm', - name: 'deployProject', - message: 'Would you like to deploy your application to Firebase Hosting?' - }) - ).then(({ deployProject }: { deployProject: boolean }) => { - if (deployProject) { - return firebaseTools.deploy({ - // tslint:disable-next-line:no-non-null-assertion - only: 'hosting:' + context.target!.project, - cwd: workspaceRoot - }); - } else { - return Promise.resolve(); - } - }); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const siteTarget = options.target ?? context.target!.project; - } else { + if (options.preview) { - return firebaseTools.deploy({ - // tslint:disable-next-line:no-non-null-assertion - only: 'hosting:' + context.target!.project, - cwd: workspaceRoot + await firebaseTools.serve({ + port: DEFAULT_EMULATOR_PORT, + host: DEFAULT_EMULATOR_HOST, + only: `hosting:${siteTarget}`, + nonInteractive: true, + projectRoot: workspaceRoot, }); + const { deployProject } = await inquirer.prompt({ + type: 'confirm', + name: 'deployProject', + message: 'Would you like to deploy your application to Firebase Hosting?' + }) as { deployProject: boolean }; + + if (!deployProject) { return; } + + process.env.FIREBASE_FRAMEWORKS_SKIP_BUILD = 'true'; + } + + return await firebaseTools.deploy({ + only: `hosting:${siteTarget}`, + cwd: workspaceRoot, + token: firebaseToken, + nonInteractive: true, + projectRoot: workspaceRoot, + }); + }; const defaultFsHost: FSHost = { moveSync, writeFileSync, - renameSync + renameSync, + copySync, + removeSync, + existsSync, }; -const getVersionRange = (v: number) => `^${v}.0.0`; +// Package managers AngularFire is willing to shell out to when resolving +// dependency versions. Kept in sync with the Angular CLI's own list. The value +// comes from `cli.packageManager` in angular.json, which this builder reads with +// a raw `JSON.parse`, i.e. it is NOT run through the Angular CLI's own schema +// validation, so it must be checked here before it is ever used as an argv0. +export const SUPPORTED_PACKAGE_MANAGERS = ['npm', 'yarn', 'pnpm', 'cnpm', 'bun']; -const findPackageVersion = (name: string) => { - const match = execSync(`npm list ${name}`).toString().match(` ${escapeRegExp(name)}@.+\\w`); - return match ? match[0].split(`${name}@`)[1] : null; +export const assertSupportedPackageManager = (packageManager: string): string => { + if (!SUPPORTED_PACKAGE_MANAGERS.includes(packageManager)) { + throw new SchematicsException( + `Unsupported package manager "${packageManager}" in angular.json (cli.packageManager). ` + + `Expected one of: ${SUPPORTED_PACKAGE_MANAGERS.join(', ')}.` + ); + } + return packageManager; }; -const getPackageJson = (context: BuilderContext, workspaceRoot: string) => { - const dependencies = { - 'firebase-admin': 'latest', - 'firebase-functions': 'latest' - }; - const devDependencies = { - 'firebase-functions-test': 'latest' - }; - Object.keys(dependencies).forEach((dependency: string) => { - const packageVersion = findPackageVersion(dependency); - if (packageVersion) { dependencies[dependency] = packageVersion; } - }); - Object.keys(devDependencies).forEach((devDependency: string) => { - const packageVersion = findPackageVersion(devDependency); - if (packageVersion) { devDependencies[devDependency] = packageVersion; } - }); +// A dependency name comes from `architect..server.options.externalDependencies` +// in angular.json. Reject anything that is not a plain package specifier so it can +// neither inject shell metacharacters (defence in depth alongside execFileSync) nor +// be parsed as a CLI flag by the package manager (argument injection). +export const assertSafeDependencyName = (name: string): string => { + // Valid npm package names / esbuild external globs never contain whitespace or + // shell metacharacters, and never start with a dash. Reject anything else so the + // value can neither inject a shell command (defence in depth alongside + // execFileSync) nor be parsed as a package-manager flag (argument injection). + if (typeof name !== 'string' || name.length === 0 || name.startsWith('-') || + /[\s;&|$`(){}<>!\\'"]/.test(name)) { + throw new SchematicsException( + `Invalid dependency name ${JSON.stringify(name)} in angular.json (server externalDependencies).` + ); + } + return name; +}; + +// All shelling out from the deploy builder funnels through this single runner. +// cross-spawn (v7) resolves the platform-appropriate executable and escapes each +// argument, so a value taken from angular.json is passed as an argv entry and can +// never be parsed as shell syntax. Unlike child_process.execFile it can launch a +// Windows `.cmd`/`.bat` shim (npm, yarn, pnpm and cnpm all ship as `.cmd` shims +// there), so `ng deploy` keeps working cross-platform. We deliberately avoid +// `shell: true`, whose args-array form Node runtime-deprecates (DEP0190) because +// it concatenates the arguments without escaping, reopening the injection. +// It is exported as an object so tests can assert the deploy code shells out only +// through here and never regresses to execSync or `shell: true`. +export const processHost = { + runPackageBin( + command: string, + args: string[], + options: SpawnSyncOptions = {}, + ): Buffer { + const result = crossSpawn.sync(command, args, options); + if (result.error) { throw result.error; } + if (result.status !== 0) { + throw new SchematicsException( + `Command "${command}" exited with ${result.signal ? `signal ${result.signal}` : `code ${result.status}`}.` + ); + } + return result.stdout; + }, +}; + +export const findPackageVersion = (packageManager: string, name: string) => { + // Run the package manager without a shell (argument array, no `shell` option) + // so a dependency name or package-manager value taken from angular.json cannot + // be interpreted as a shell command. Both values are validated first, so an + // unsupported manager or unsafe name throws before anything is ever spawned. + const output = processHost.runPackageBin(assertSupportedPackageManager(packageManager), [ + 'list', + assertSafeDependencyName(name), + ]).toString(); + const match = output.match(`[^|s]${escapeRegExp(name)}[@| ][^s]+(s.+)?$`); + return match ? match[0].split(new RegExp(`${escapeRegExp(name)}[@| ]`))[1].split(/\s/)[0] : null; +}; + +const getPackageJson = (context: BuilderContext, workspaceRoot: string, options: DeployBuilderOptions, main?: string) => { + const dependencies: Record = {}; + const devDependencies: Record = {}; + const { firebaseFunctionsDependencies } = readJsonSync(join(moduleDirectory, '..', 'versions.json')); + if (options.ssr !== 'cloud-run') { + Object.keys(firebaseFunctionsDependencies).forEach(name => { + const { version, dev } = firebaseFunctionsDependencies[name]; + (dev ? devDependencies : dependencies)[name] = version; + }); + } if (existsSync(join(workspaceRoot, 'angular.json'))) { const angularJson = JSON.parse(readFileSync(join(workspaceRoot, 'angular.json')).toString()); - // tslint:disable-next-line:no-non-null-assertion + const packageManager = angularJson.cli?.packageManager ?? 'npm'; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const server = angularJson.projects[context.target!.project].architect.server; - const serverOptions = server && server.options; - const externalDependencies = serverOptions && serverOptions.externalDependencies || []; - const bundleDependencies = serverOptions && serverOptions.bundleDependencies; - if (bundleDependencies !== true) { + const externalDependencies = server?.options?.externalDependencies || []; + const bundleDependencies = server?.options?.bundleDependencies ?? true; + if (bundleDependencies) { + externalDependencies.forEach(externalDependency => { + const packageVersion = findPackageVersion(packageManager, externalDependency); + if (packageVersion) { dependencies[externalDependency] = packageVersion; } + }); + } else { if (existsSync(join(workspaceRoot, 'package.json'))) { const packageJson = JSON.parse(readFileSync(join(workspaceRoot, 'package.json')).toString()); Object.keys(packageJson.dependencies).forEach((dependency: string) => { dependencies[dependency] = packageJson.dependencies[dependency]; }); } // TODO should we throw? - } else { - externalDependencies.forEach(externalDependency => { - const packageVersion = findPackageVersion(externalDependency); - if (packageVersion) { dependencies[externalDependency] = packageVersion; } - }); } - } // TODO should we throw? - return defaultPackage(dependencies, devDependencies); + } + // TODO should we throw? + return defaultPackage(dependencies, devDependencies, options, main); }; export const deployToFunction = async ( firebaseTools: FirebaseTools, context: BuilderContext, workspaceRoot: string, - project: experimental.workspace.WorkspaceTool, - preview: boolean, + staticBuildTarget: BuildTarget, + serverBuildTarget: BuildTarget, + options: DeployBuilderOptions, + firebaseToken?: string, fsHost: FSHost = defaultFsHost ) => { - if (!satisfies(process.versions.node, getVersionRange(NODE_VERSION))) { + + const staticBuildOptions = await context.getTargetOptions(targetFromTargetString(staticBuildTarget.name)); + if (!staticBuildOptions.outputPath || typeof staticBuildOptions.outputPath !== 'string') { + throw new Error( + `Cannot read the output path option of the Angular project '${staticBuildTarget.name}' in angular.json` + ); + } + assertSafeOutputPath(staticBuildOptions.outputPath, staticBuildTarget.name); + + const serverBuildOptions = await context.getTargetOptions(targetFromTargetString(serverBuildTarget.name)); + if (!serverBuildOptions.outputPath || typeof serverBuildOptions.outputPath !== 'string') { + throw new Error( + `Cannot read the output path option of the Angular project '${serverBuildTarget.name}' in angular.json` + ); + } + assertSafeOutputPath(serverBuildOptions.outputPath, serverBuildTarget.name); + + const staticOut = join(workspaceRoot, staticBuildOptions.outputPath); + const serverOut = join(workspaceRoot, serverBuildOptions.outputPath); + + const functionsOut = options.outputPath ? join(workspaceRoot, options.outputPath) : dirname(serverOut); + assertSafeFunctionName(options.functionName); + const functionName = options.functionName || DEFAULT_FUNCTION_NAME; + + const newStaticOut = join(functionsOut, staticBuildOptions.outputPath); + const newServerOut = join(functionsOut, serverBuildOptions.outputPath); + + // New behavior vs. old + if (options.outputPath) { + fsHost.removeSync(functionsOut); + fsHost.copySync(staticOut, newStaticOut); + fsHost.copySync(serverOut, newServerOut); + } else { + fsHost.moveSync(staticOut, newStaticOut); + fsHost.moveSync(serverOut, newServerOut); + } + + const packageJson = getPackageJson(context, workspaceRoot, options); + const nodeVersion = packageJson.engines.node; + + if (!satisfies(process.versions.node, nodeVersion.toString())) { context.logger.warn( - `âš ī¸ Your Node.js version (${process.versions.node}) does not match the Firebase Functions runtime (${NODE_VERSION}).` + `âš ī¸ Your Node.js version (${process.versions.node}) does not match the Firebase Functions runtime (${nodeVersion}).` ); } - if ( - !project || - !project.build || - !project.build.options || - !project.build.options.outputPath - ) { - throw new SchematicsException( - `Cannot read the output path (architect.build.options.outputPath) of the Angular project in angular.json` + const functionsPackageJsonPath = join(functionsOut, 'package.json'); + fsHost.writeFileSync( + functionsPackageJsonPath, + JSON.stringify(packageJson, null, 2) + ); + + if (options.CF3v2) { + fsHost.writeFileSync( + join(functionsOut, 'index.js'), + functionGen2(serverBuildOptions.outputPath, options, functionName) + ); + } else { + fsHost.writeFileSync( + join(functionsOut, 'index.js'), + defaultFunction(serverBuildOptions.outputPath, options, functionName) ); } - if ( - !project || - !project.server || - !project.server.options || - !project.server.options.outputPath - ) { - throw new SchematicsException( - `Cannot read the output path (architect.server.options.outputPath) of the Angular project in angular.json` + if (!options.prerender) { + try { + fsHost.renameSync( + join(newStaticOut, 'index.html'), + join(newStaticOut, 'index.original.html') + ); + } catch (_) { /* empty */ } + } + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const siteTarget = options.target ?? context.target!.project; + + if (fsHost.existsSync(functionsPackageJsonPath)) { + // Pass the output directory as an argv entry rather than interpolating it + // into a shell string; `functionsOut` derives from the `outputPath` deploy + // option (angular.json) and must not be able to inject shell commands. npm is + // a `.cmd` shim on Windows, which child_process.execFile cannot launch, so + // this goes through the shell-free cross-spawn runner instead. + processHost.runPackageBin('npm', ['--prefix', functionsOut, 'install'], { stdio: 'inherit' }); + } else { + console.error(`No package.json exists at ${functionsOut}`); + } + + if (options.preview) { + + await firebaseTools.serve({ + port: DEFAULT_EMULATOR_PORT, + host: DEFAULT_EMULATOR_HOST, + targets: [`hosting:${siteTarget}`, `functions:${functionName}`], + nonInteractive: true, + projectRoot: workspaceRoot, + }); + + const { deployProject } = await inquirer.prompt({ + type: 'confirm', + name: 'deployProject', + message: 'Would you like to deploy your application to Firebase Hosting & Cloud Functions?' + }) as { deployProject: boolean }; + + if (!deployProject) { return; } + } + + return await firebaseTools.deploy({ + only: `hosting:${siteTarget},functions:${functionName}`, + cwd: workspaceRoot, + token: firebaseToken, + nonInteractive: true, + projectRoot: workspaceRoot, + }); + +}; + + +// Exported (rather than kept private) so the argv shape can be asserted directly in tests, +// without having to mock child_process.spawn. +export const buildCloudRunBuildsSubmitArgs = ( + cloudRunOut: string, + serviceId: string, + options: DeployBuilderOptions +): string[] => [ + 'builds', 'submit', cloudRunOut, + '--tag', `gcr.io/${options.firebaseProject}/${serviceId}`, + '--project', options.firebaseProject, + '--quiet', +]; + +export const buildCloudRunDeployArgs = ( + serviceId: string, + options: DeployBuilderOptions, + deployArguments: string[] +): string[] => [ + 'run', 'deploy', serviceId, + '--image', `gcr.io/${options.firebaseProject}/${serviceId}`, + '--project', options.firebaseProject, + ...deployArguments, + '--platform', 'managed', + '--allow-unauthenticated', + '--region', options.region, + '--quiet', +]; + +export const deployToCloudRun = async ( + firebaseTools: FirebaseTools, + context: BuilderContext, + workspaceRoot: string, + staticBuildTarget: BuildTarget, + serverBuildTarget: BuildTarget, + options: DeployBuilderOptions, + firebaseToken?: string, + fsHost: FSHost = defaultFsHost +) => { + + const staticBuildOptions = await context.getTargetOptions(targetFromTargetString(staticBuildTarget.name)); + if (!staticBuildOptions.outputPath || typeof staticBuildOptions.outputPath !== 'string') { + throw new Error( + `Cannot read the output path option of the Angular project '${staticBuildTarget.name}' in angular.json` ); } + assertSafeOutputPath(staticBuildOptions.outputPath, staticBuildTarget.name); + + const serverBuildOptions = await context.getTargetOptions(targetFromTargetString(serverBuildTarget.name)); + if (!serverBuildOptions.outputPath || typeof serverBuildOptions.outputPath !== 'string') { + throw new Error( + `Cannot read the output path option of the Angular project '${serverBuildTarget.name}' in angular.json` + ); + } + assertSafeOutputPath(serverBuildOptions.outputPath, serverBuildTarget.name); + // Checked here, alongside the outputPath screens, rather than next to the Dockerfile it + // guards: everything below wipes and refills the output directory, so rejecting late + // would leave that directory half-written before throwing. + assertSafeNodeVersion(options.functionsNodeVersion); + + const staticOut = join(workspaceRoot, staticBuildOptions.outputPath); + const serverOut = join(workspaceRoot, serverBuildOptions.outputPath); - const staticOut = project.build.options.outputPath; - const serverOut = project.server.options.outputPath; - const newClientPath = join(dirname(staticOut), staticOut); - const newServerPath = join(dirname(serverOut), serverOut); + const cloudRunOut = options.outputPath ? join(workspaceRoot, options.outputPath) : join(dirname(serverOut), 'run'); + const serviceId = options.functionName || DEFAULT_FUNCTION_NAME; + + const newStaticOut = join(cloudRunOut, staticBuildOptions.outputPath); + const newServerOut = join(cloudRunOut, serverBuildOptions.outputPath); // This is needed because in the server output there's a hardcoded dependency on $cwd/dist/browser, // This assumes that we've deployed our application dist directory and we're running the server // in the parent directory. To have this precondition, we move dist/browser to dist/dist/browser // since the firebase function runs the server from dist. - fsHost.moveSync(staticOut, newClientPath); - fsHost.moveSync(serverOut, newServerPath); + fsHost.removeSync(cloudRunOut); + fsHost.copySync(staticOut, newStaticOut); + fsHost.copySync(serverOut, newServerOut); + + // Target runtime is Linux based. + const packageJson = getPackageJson(context, workspaceRoot, options, [serverBuildOptions.outputPath, 'main.js'].join('/')); + const nodeVersion = packageJson.engines.node; + + if (!satisfies(process.versions.node, nodeVersion.toString())) { + context.logger.warn( + `âš ī¸ Your Node.js version (${process.versions.node}) does not match the Cloud Run runtime (${nodeVersion}).` + ); + } fsHost.writeFileSync( - join(dirname(serverOut), 'package.json'), - getPackageJson(context, workspaceRoot) + join(cloudRunOut, 'package.json'), + JSON.stringify(packageJson, null, 2), ); fsHost.writeFileSync( - join(dirname(serverOut), 'index.js'), - defaultFunction(serverOut) + join(cloudRunOut, 'Dockerfile'), + dockerfile(options) ); - fsHost.renameSync( - join(newClientPath, 'index.html'), - join(newClientPath, 'index.original.html') - ); + if (!options.prerender) { + try { + fsHost.renameSync( + join(newStaticOut, 'index.html'), + join(newStaticOut, 'index.original.html') + ); + } catch (_) { /* empty */ } + } - if (preview) { - const port = 5000; // TODO make this configurable - - setTimeout(() => { - open(`http://localhost:${port}`); - }, 1500); - - return firebaseTools.serve({ port, targets: ['hosting', 'functions'], host: 'localhost'}).then(() => - require('inquirer').prompt({ - type: 'confirm', - name: 'deployProject', - message: 'Would you like to deploy your application to Firebase Hosting & Cloud Functions?' - }) - ).then(({ deployProject }: { deployProject: boolean }) => { - if (deployProject) { - return firebaseTools.deploy({ - // tslint:disable-next-line:no-non-null-assertion - only: `hosting:${context.target!.project},functions:ssr`, - cwd: workspaceRoot - }); - } else { - return Promise.resolve(); - } - }); - } else { - return firebaseTools.deploy({ - // tslint:disable-next-line:no-non-null-assertion - only: `hosting:${context.target!.project},functions:ssr`, - cwd: workspaceRoot - }); + if (options.preview) { + throw new SchematicsException('Cloud Run preview not supported.'); } + + const deployArguments: string[] = []; + const cloudRunOptions = options.cloudRunOptions || {}; + Object.entries(DEFAULT_CLOUD_RUN_OPTIONS).forEach(([k, v]) => { + cloudRunOptions[k] ||= v; + }); + // lean on the schema for validation (rather than sanitize) + if (cloudRunOptions.cpus) { deployArguments.push('--cpu', cloudRunOptions.cpus.toString()); } + if (cloudRunOptions.maxConcurrency) { deployArguments.push('--concurrency', cloudRunOptions.maxConcurrency.toString()); } + if (cloudRunOptions.maxInstances) { deployArguments.push('--max-instances', cloudRunOptions.maxInstances.toString()); } + if (cloudRunOptions.memory) { deployArguments.push('--memory', cloudRunOptions.memory.toString()); } + if (cloudRunOptions.minInstances) { deployArguments.push('--min-instances', cloudRunOptions.minInstances.toString()); } + if (cloudRunOptions.timeout) { deployArguments.push('--timeout', cloudRunOptions.timeout.toString()); } + if (cloudRunOptions.vpcConnector) { deployArguments.push('--vpc-connector', cloudRunOptions.vpcConnector); } + + // TODO validate firebaseProject, vpcConnector, and the outputPath deploy option both to + // limit errors and opp for injection + + context.logger.info(`đŸ“Ļ Deploying to Cloud Run`); + await spawnAsync('gcloud', buildCloudRunBuildsSubmitArgs(cloudRunOut, serviceId, options)); + await spawnAsync('gcloud', buildCloudRunDeployArgs(serviceId, options, deployArguments)); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const siteTarget = options.target ?? context.target!.project; + + // TODO deploy cloud run + return await firebaseTools.deploy({ + only: `hosting:${siteTarget}`, + cwd: workspaceRoot, + token: firebaseToken, + nonInteractive: true, + projectRoot: workspaceRoot, + }); }; export default async function deploy( firebaseTools: FirebaseTools, context: BuilderContext, - projectTargets: experimental.workspace.WorkspaceTool, - buildTargets: BuildTarget[], + staticBuildTarget: BuildTarget, + serverBuildTarget: BuildTarget | undefined, + prerenderBuildTarget: BuildTarget | undefined, firebaseProject: string, - ssr: boolean, - preview: boolean + options: DeployBuilderOptions, + firebaseToken?: string, ) { - await firebaseTools.login(); + const legacyNgDeploy = !options.version || options.version < 2; - if (!context.target) { - throw new Error('Cannot execute the build target'); + if (!firebaseToken && !process.env.GOOGLE_APPLICATION_CREDENTIALS) { + await firebaseTools.login(); + const user = await firebaseTools.login({ projectRoot: context.workspaceRoot }); + console.log(`Logged into Firebase as ${user.email}.`); } - context.logger.info(`đŸ“Ļ Building "${context.target.project}"`); + if (!firebaseToken && process.env.GOOGLE_APPLICATION_CREDENTIALS) { + await spawnAsync('gcloud', ['auth', 'activate-service-account', '--key-file', process.env.GOOGLE_APPLICATION_CREDENTIALS]); + console.log(`Using Google Application Credentials.`); + } - for (const target of buildTargets) { + if (legacyNgDeploy) { + console.error(`Legacy ng-deploy Firebase is deprecated. +Please migrate to Firebase Hosting's integration with Angular https://firebase.google.com/docs/hosting/frameworks/angular +or the new Firebase App Hosting product https://firebase.google.com/docs/app-hosting`); + } + + if (prerenderBuildTarget) { const run = await context.scheduleTarget( - targetFromTargetString(target.name), - target.options + targetFromTargetString(prerenderBuildTarget.name), + prerenderBuildTarget.options ); await run.result; + + } else { + + if (!context.target) { + throw new Error('Cannot execute the build target'); + } + + context.logger.info(`đŸ“Ļ Building "${context.target.project}"`); + + const builders = [ + context.scheduleTarget( + targetFromTargetString(staticBuildTarget.name), + staticBuildTarget.options + ).then(run => run.result) + ]; + + if (serverBuildTarget) { + builders.push(context.scheduleTarget( + targetFromTargetString(serverBuildTarget.name), + serverBuildTarget.options + ).then(run => run.result)); + } + + await Promise.all(builders); } + try { - await firebaseTools.use(firebaseProject, { project: firebaseProject }); - } catch (e) { + await firebaseTools.use(firebaseProject, { + project: firebaseProject, + projectRoot: context.workspaceRoot, + }); + } catch (_) { throw new Error(`Cannot select firebase project '${firebaseProject}'`); } - try { - const winston = require('winston'); - const tripleBeam = require('triple-beam'); - - firebaseTools.logger.add( - new winston.transports.Console({ - level: 'info', - format: winston.format.printf((info) => - [info.message, ...(info[tripleBeam.SPLAT] || [])] - .filter((chunk) => typeof chunk === 'string') - .join(' ') - ) - }) - ); + options.firebaseProject = firebaseProject; + + const logger = new winston.transports.Console({ + level: 'info', + format: winston.format.printf((info) => { + const emulator = info[tripleBeam.SPLAT as any]?.[1]?.metadata?.emulator; + const text = info[tripleBeam.SPLAT as any]?.[0]; + if (text?.replace) { + // eslint-disable-next-line no-control-regex + const plainText = text.replace(/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]/g, ''); + if (emulator?.name === 'hosting' && plainText.startsWith('Local server: ')) { + open(plainText.split(': ')[1]); + } + } + return [info.message, ...(info[tripleBeam.SPLAT as any] || []) as any] + .filter((chunk) => typeof chunk === 'string') + .join(' '); + }) + }); - if (ssr) { - await deployToFunction( + firebaseTools.logger.logger.add(logger); + + if (legacyNgDeploy && serverBuildTarget) { + if (options.ssr === 'cloud-run') { + await deployToCloudRun( firebaseTools, context, context.workspaceRoot, - projectTargets, - preview + staticBuildTarget, + serverBuildTarget, + options, + firebaseToken, ); } else { - await deployToHosting( + await deployToFunction( firebaseTools, context, context.workspaceRoot, - preview + staticBuildTarget, + serverBuildTarget, + options, + firebaseToken, ); } - - } catch (e) { - context.logger.error(e.message || e); + } else { + await deployToHosting( + firebaseTools, + context, + context.workspaceRoot, + options, + firebaseToken, + ); } + } diff --git a/src/schematics/deploy/builder.ts b/src/schematics/deploy/builder.ts index b47b9e130..6708de951 100644 --- a/src/schematics/deploy/builder.ts +++ b/src/schematics/deploy/builder.ts @@ -1,64 +1,65 @@ import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect'; -import { NodeJsSyncHost } from '@angular-devkit/core/node'; -import deploy from './actions'; -import { experimental, json, normalize } from '@angular-devkit/core'; -import { BuildTarget, DeployBuilderSchema } from '../interfaces'; -import { getFirebaseProjectName } from '../utils'; - -type DeployBuilderOptions = DeployBuilderSchema & json.JsonObject; +import { getFirebaseTools } from '../firebaseTools'; +import { BuildTarget } from '../interfaces'; +import { getFirebaseProjectNameFromFs } from '../utils'; +import deploy, { DeployBuilderOptions } from './actions'; // Call the createBuilder() function to create a builder. This mirrors // createJobHandler() but add typings specific to Architect Builders. -export default createBuilder( +export default createBuilder( async (options: DeployBuilderOptions, context: BuilderContext): Promise => { - // The project root is added to a BuilderContext. - const root = normalize(context.workspaceRoot); - const workspace = new experimental.workspace.Workspace( - root, - new NodeJsSyncHost() - ); - await workspace - .loadWorkspaceFromHost(normalize('angular.json')) - .toPromise(); - if (!context.target) { throw new Error('Cannot deploy the application without a target'); } - const projectTargets = workspace.getProjectTargets(context.target.project); - - const firebaseProject = getFirebaseProjectName( + const [defaultFirebaseProject, defaultFirebaseHostingSite] = getFirebaseProjectNameFromFs( context.workspaceRoot, context.target.project ); + const firebaseProject = options.firebaseProject || defaultFirebaseProject; if (!firebaseProject) { - throw new Error('Cannot find firebase project for your app in .firebaserc'); + throw new Error('Cannot determine the Firebase Project from your angular.json or .firebaserc'); + } + if (firebaseProject !== defaultFirebaseProject) { + throw new Error('The Firebase Project specified by your angular.json or .firebaserc is in conflict'); } - const buildTarget = options.buildTarget || `${context.target.project}:build:production`; + const firebaseHostingSite = options.firebaseHostingSite || defaultFirebaseHostingSite; + if (!firebaseHostingSite) { + throw new Error(`Cannot determine the Firebase Hosting Site from your angular.json or .firebaserc`); + } + if (firebaseHostingSite !== defaultFirebaseHostingSite) { + throw new Error('The Firebase Hosting Site specified by your angular.json or .firebaserc is in conflict'); + } + + const staticBuildTarget = { name: options.browserTarget || options.buildTarget || `${context.target.project}:build:production` }; + + let prerenderBuildTarget: BuildTarget | undefined; + if (options.prerender) { + prerenderBuildTarget = { + name: options.prerenderTarget || `${context.target.project}:prerender:production` + }; + } - const targets: BuildTarget[] = [{ - name: buildTarget - }]; + let serverBuildTarget: BuildTarget | undefined; if (options.ssr) { - targets.push({ - name: options.universalBuildTarget || `${context.target.project}:server:production`, - options: { - bundleDependencies: 'all' - } - }); + serverBuildTarget = { + name: options.serverTarget || options.universalBuildTarget || `${context.target.project}:server:production` + }; } try { + process.env.FIREBASE_DEPLOY_AGENT = 'angularfire'; await deploy( - require('firebase-tools'), + (await getFirebaseTools()), context, - projectTargets, - targets, + staticBuildTarget, + serverBuildTarget, + prerenderBuildTarget, firebaseProject, - !!options.ssr, - !!options.preview + options, + process.env.FIREBASE_TOKEN, ); } catch (e) { console.error('Error when trying to deploy: '); diff --git a/src/schematics/deploy/functions-templates.jasmine.ts b/src/schematics/deploy/functions-templates.jasmine.ts new file mode 100644 index 000000000..0477ae3c3 --- /dev/null +++ b/src/schematics/deploy/functions-templates.jasmine.ts @@ -0,0 +1,44 @@ +import { DEFAULT_NODE_VERSION, defaultFunction, defaultPackage } from './functions-templates.js'; +import 'jasmine'; + +describe('functions templates', () => { + describe('defaultFunction', () => { + it('requires the firebase-functions/v1 subpath, where the v1 API lives on firebase-functions 6', () => { + const generated = defaultFunction('dist/app', {}, undefined); + expect(generated).toContain(`require('firebase-functions/v1')`); + expect(generated).not.toContain(`require('firebase-functions')`); + }); + + it('escapes region rather than interpolating it into a string literal', () => { + const region = `us-central1'); require('child_process').execSync('id'); ('`; + const generated = defaultFunction('dist/app', { region }, undefined); + expect(generated).toContain(`.region(${JSON.stringify(region)})`); + // Interpolated raw, the payload closes `.region('` and the require becomes a statement + // of its own. Escaped, it changes nothing but that one argument, so swapping it back + // out has to reproduce the benign render exactly. + expect(generated.replace(JSON.stringify(region), JSON.stringify('us-central1'))) + .toBe(defaultFunction('dist/app', { region: 'us-central1' }, undefined)); + }); + }); + + describe('defaultPackage', () => { + it('defaults engines.node to a runtime Cloud Functions still accepts', () => { + const generated = defaultPackage({}, {}, {}); + expect(generated.engines.node).toBe(DEFAULT_NODE_VERSION.toString()); + expect(DEFAULT_NODE_VERSION).toBe(22); + }); + + it('quotes the start script path, which a shell would otherwise split or expand', () => { + // `main` carries a build target's outputPath, and the Cloud Run image runs this + // through `npm start`. + expect(defaultPackage({}, {}, {}, 'dist/my app/main.js').scripts.start) + .toBe('node "dist/my app/main.js"'); + expect(defaultPackage({}, {}, {}, 'dist/[ab]/main.js').scripts.start) + .toBe('node "dist/[ab]/main.js"'); + }); + + it('falls back to the functions shell when there is no main', () => { + expect(defaultPackage({}, {}, {}).scripts.start).toBe('firebase functions:shell'); + }); + }); +}); diff --git a/src/schematics/deploy/functions-templates.ts b/src/schematics/deploy/functions-templates.ts index 6afe45cd8..f3567545a 100644 --- a/src/schematics/deploy/functions-templates.ts +++ b/src/schematics/deploy/functions-templates.ts @@ -1,44 +1,84 @@ -// TODO allow these to be configured -export const NODE_VERSION = 10; -const FUNCTION_NAME = 'ssr'; -const FUNCTION_REGION = 'us-central1'; -const RUNTIME_OPTIONS = { +import { DeployBuilderOptions } from './actions'; + +// Must be a runtime Cloud Functions still accepts, and must satisfy the engines of @angular/core +// (the function runs the Angular server) and of firebase-admin. The `dockerfile` template below +// also drops it into `FROM node:-slim`, so it picks the base image on the Cloud Run path. +export const DEFAULT_NODE_VERSION = 22; +export const DEFAULT_FUNCTION_NAME = 'ssr'; + +const DEFAULT_FUNCTION_REGION = 'us-central1'; + +const DEFAULT_RUNTIME_OPTIONS = { timeoutSeconds: 60, memory: '1GB' }; export const defaultPackage = ( - dependencies: {[key: string]: string}, - devDependencies: {[key: string]: string} -) => `{ - "name": "functions", - "description": "Angular Universal Application", - "scripts": { - "lint": "", - "serve": "firebase serve --only functions", - "shell": "firebase functions:shell", - "start": "npm run shell", - "deploy": "firebase deploy --only functions", - "logs": "firebase functions:log" + dependencies: Record, + devDependencies: Record, + options: DeployBuilderOptions, + main?: string, +) => ({ + name: 'functions', + description: 'Angular Universal Application', + main: main ?? 'index.js', + scripts: { + // Quoted: `npm start` hands this to a shell, and `main` carries a build target's + // outputPath, so an unquoted path with a space in it splits and one with a glob + // character in it expands before node ever sees it. + start: main ? `node "${main}"` : 'firebase functions:shell', }, - "engines": { - "node": "${NODE_VERSION}" + engines: { + node: (options.functionsNodeVersion || DEFAULT_NODE_VERSION).toString() }, - "dependencies": ${JSON.stringify(dependencies, null, 4)}, - "devDependencies": ${JSON.stringify(devDependencies, null, 4)}, - "private": true -} -`; + dependencies, + devDependencies, + private: true +}); +// `.region()` lives on the /v1 subpath since firebase-functions 6. Requiring the package root +// instead still resolves, and fails only when the deployed function serves its first request. export const defaultFunction = ( - path: string -) => `const functions = require('firebase-functions'); + path: string, + options: DeployBuilderOptions, + functionName: string|undefined, +) => `const functions = require('firebase-functions/v1'); + +// Increase readability in Cloud Logging +require("firebase-functions/logger/compat"); const expressApp = require('./${path}/main').app(); -exports.${FUNCTION_NAME} = functions - .region('${FUNCTION_REGION}') - .runWith(${JSON.stringify(RUNTIME_OPTIONS)}) +exports.${functionName || DEFAULT_FUNCTION_NAME} = functions + .region(${JSON.stringify(options.region || DEFAULT_FUNCTION_REGION)}) + .runWith(${JSON.stringify(options.functionsRuntimeOptions || DEFAULT_RUNTIME_OPTIONS)}) .https .onRequest(expressApp); `; + +export const functionGen2 = ( + path: string, + options: DeployBuilderOptions, + functionName: string|undefined, +) => `const { onRequest } = require('firebase-functions/v2/https'); + +// Increase readability in Cloud Logging +require("firebase-functions/logger/compat"); + +const expressApp = require('./${path}/main').app(); + +exports.${functionName || DEFAULT_FUNCTION_NAME} = onRequest(${JSON.stringify({ + region: options.region || DEFAULT_FUNCTION_REGION, + ...(options.functionsRuntimeOptions || DEFAULT_RUNTIME_OPTIONS) +})}, expressApp); +`; + +export const dockerfile = ( + options: DeployBuilderOptions, +) => `FROM node:${options.functionsNodeVersion || DEFAULT_NODE_VERSION}-slim +WORKDIR /usr/src/app +COPY package*.json ./ +RUN npm install --only=production +COPY . ./ +CMD [ "npm", "start" ] +`; diff --git a/src/schematics/deploy/schema.json b/src/schematics/deploy/schema.json index a48deed07..ff8d0bc11 100644 --- a/src/schematics/deploy/schema.json +++ b/src/schematics/deploy/schema.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema", - "id": "FirebaseDeploySchema", + "$id": "FirebaseDeploySchema", "title": "Firebase Deploy", "description": "Ng Deploy target options for Firebase.", "properties": { @@ -9,9 +9,114 @@ "description": "Target to build.", "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$" }, + "browserTarget": { + "type": "string", + "description": "Target to build.", + "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$" + }, + "prerenderTarget": { + "type": "string", + "description": "Target to build.", + "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$" + }, + "serverTarget": { + "type": "string", + "description": "Target to build.", + "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$" + }, + "universalBuildTarget": { + "type": "string", + "description": "Target to build.", + "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$" + }, + "ssr": { + "oneOf": [{ "type": "boolean" }, { "type": "string" }], + "description": "Should we attempt to deploy the function to Cloud Functions (true or 'cloud-functions') / Cloud Run ('cloud-run') or just Hosting (false)" + }, + "prerender": { + "type": "boolean", + "description": "Prerender before deploy?" + }, + "firebaseProject": { + "type": "string", + "description": "The Firebase project name or project alias to use when deploying" + }, + "target": { + "type": "string", + "description": "The Firebase hosting target in firebase.json for multi-site" + }, + "firebaseHostingSite": { + "type": "string", + "description": "The Firebase Hosting site to deploy to" + }, + "functionName": { + "type": "string", + "pattern": "^[A-Za-z][A-Za-z0-9_-]{0,62}$", + "description": "The name of the Cloud Function or Cloud Run serviceId to deploy SSR to. Must start with a letter and contain only letters, numbers, hyphens and underscores; on the Cloud Functions path this value also becomes a JavaScript identifier, so use only letters and numbers there." + }, + "functionsNodeVersion": { + "oneOf": [{ "type": "number" }, { "type": "string" }], + "pattern": "^(?!latest$)[\\w][\\w.-]{0,127}$", + "description": "Version of Node.js to run Cloud Functions / Run on, e.g. 22. On Cloud Run this is the tag of the node base image (node:-slim), so any tag that image publishes works, including lts and 22-bookworm; latest is excluded because its slim variant is published as node:slim. On Cloud Functions the value becomes the engines.node field, which firebase-tools resolves to a nodejs runtime by concatenation, so a plain major version is the only shape that works there. Not a semver range: >=18 and ^20 are rejected outright, and 20.x, though a grammatical tag, matches no published image." + }, + "CF3v2": { + "type": "boolean", + "description": "Generation of the Functions product to run on" + }, + "region": { + "type": "string", + "pattern": "^[a-z]+-[a-z]+\\d+$", + "description": "The region to deploy Cloud Functions or Cloud Run to, e.g. us-central1" + }, + "outputPath": { + "type": "string", + "description": "Where to output the deploy artifacts" + }, + "functionsRuntimeOptions": { + "type": "object", + "description": "Runtime options for Cloud Functions, if deploying to Cloud Functions" + }, "preview": { "type": "boolean", "description": "Do not deploy the application, just set up the Firebase Function in the project output directory. Can be used for testing the Firebase Function with `firebase serve`." + }, + "cloudRunOptions": { + "type": "object", + "description": "Options passed to Cloud Run, if deploying to Cloud Run.", + "properties": { + "cpus": { + "type": "number", + "description": "Set a CPU limit in Kubernetes cpu units." + }, + "maxConcurrency": { + "oneOf": [{ "type": "number" }, { "type": "string" }], + "pattern": "^(\\d+|default)$", + "description": "Set the maximum number of concurrent requests allowed per container instance. If concurrency is unspecified, any number of concurrent requests are allowed. To unset this field, provide the special value default." + }, + "maxInstances": { + "oneOf": [{ "type": "number" }, { "type": "string" }], + "pattern": "^(\\d+|default)$", + "description": "The maximum number of container instances of the Service to run. Use 'default' to unset the limit and use the platform default." + }, + "memory": { + "type": "string", + "pattern": "^\\d+(G|M)i$", + "description": "Set a memory limit. Ex: 1Gi, 512Mi." + }, + "minInstances": { + "oneOf": [{ "type": "number" }, { "type": "string" }], + "pattern": "^(\\d+|default)$", + "description": "The minimum number of container instances of the Service to run or 'default' to remove any minimum." + }, + "timeout": { + "type": "number", + "description": "Set the maximum request execution time (timeout) in seconds." + }, + "vpcConnector": { + "type": "string", + "description": "Set a VPC connector for this resource." + } + } } } } diff --git a/src/schematics/firebaseTools.ts b/src/schematics/firebaseTools.ts new file mode 100644 index 000000000..be6553c8e --- /dev/null +++ b/src/schematics/firebaseTools.ts @@ -0,0 +1,55 @@ + +import { execSync, spawn } from 'child_process'; +import ora from 'ora'; +import { compare as semverCompare } from 'semver'; +import { FirebaseTools } from './interfaces'; + +declare global { + var firebaseTools: FirebaseTools|undefined; +} + +// The minimum firebase-tools version the schematics require. +const minFirebaseToolsVersion = '14.0.0'; + +export const getFirebaseTools = () => globalThis.firebaseTools ? + Promise.resolve(globalThis.firebaseTools) : + new Promise((resolve, reject) => { + process.env.FIREBASE_CLI_EXPERIMENTS ||= 'webframeworks'; + try { + resolve(require('firebase-tools')); + } catch (_) { + try { + const root = execSync('npm root --location=global').toString().trim(); + resolve(require(`${root}/firebase-tools`)); + } catch (_) { + const spinner = ora({ + text: `Installing firebase-tools...`, + // Workaround for https://github.com/sindresorhus/ora/issues/136. + discardStdin: process.platform !== 'win32', + }).start(); + spawn('npm', ['i', '--location=global', 'firebase-tools'], { + stdio: 'pipe', + shell: true, + }).on('close', (code) => { + if (code === 0) { + spinner.succeed('firebase-tools installed globally.'); + spinner.stop(); + const root = execSync('npm root -g').toString().trim(); + resolve(require(`${root}/firebase-tools`)); + } else { + spinner.fail('Package install failed.'); + reject(); + } + }); + } + } + }).then(firebaseTools => { + globalThis.firebaseTools = firebaseTools; + const version = firebaseTools.cli.version(); + console.log(`Using firebase-tools version ${version}`); + if (semverCompare(version, minFirebaseToolsVersion) === -1) { + console.error(`firebase-tools version ${minFirebaseToolsVersion}+ is required, please upgrade and run again`); + return Promise.reject(); + } + return firebaseTools; + }); diff --git a/src/schematics/interfaces.ts b/src/schematics/interfaces.ts index f3d2eda83..3d34ef332 100644 --- a/src/schematics/interfaces.ts +++ b/src/schematics/interfaces.ts @@ -1,32 +1,139 @@ -export interface Project { +import type { HttpsOptions } from 'firebase-functions/https'; + +export const enum FEATURES { + Authentication, + Analytics, + AppCheck, + Database, + DataConnect, + Functions, + Messaging, + Performance, + Firestore, + Storage, + RemoteConfig, + AI, +} + +export const featureOptions = [ + { name: 'Authentication', value: FEATURES.Authentication }, + { name: 'Google Analytics', value: FEATURES.Analytics }, + { name: 'App Check', value: FEATURES.AppCheck }, + { name: 'Firestore', value: FEATURES.Firestore }, + { name: 'Realtime Database', value: FEATURES.Database }, + { name: 'Data Connect', value: FEATURES.DataConnect }, + { name: 'Cloud Functions (callable)', value: FEATURES.Functions }, + { name: 'Cloud Messaging', value: FEATURES.Messaging }, + { name: 'Performance Monitoring', value: FEATURES.Performance }, + { name: 'Cloud Storage', value: FEATURES.Storage }, + { name: 'Remote Config', value: FEATURES.RemoteConfig }, + { name: 'AI Logic', value: FEATURES.AI }, +]; + +export const enum PROJECT_TYPE { Static, CloudFunctions, CloudRun, WebFrameworks } + +export interface NgAddOptions { + firebaseProject: string; + project?: string; +} + +export interface NgAddNormalizedOptions { + project: string; + firebaseProject: FirebaseProject; + firebaseApp: FirebaseApp|undefined; + firebaseHostingSite: FirebaseHostingSite|undefined; + sdkConfig: Record|undefined; + buildTarget: [string,string]|undefined; + serveTarget: [string,string]|undefined; + ssrRegion: string|undefined; +} + +export interface DeployOptions { + project?: string; +} + +export interface FirebaseProject { projectId: string; projectNumber: string; displayName: string; name: string; - resources: { [key: string]: string }; + resources: Record; + state: string; } export interface FirebaseDeployConfig { cwd: string; only?: string; + token?: string; + [key: string]: any; +} + +export interface FirebaseApp { + name: string; + displayName: string; + platform: string; + appId: string; + namespace: string; +} + +export interface FirebaseHostingSite { + name: string; + defaultUrl: string; + type: string; + appId: string|undefined; +} + +export interface FirebaseSDKConfig { + fileName: string; + fileContents: string; + sdkConfig: Record; } export interface FirebaseTools { projects: { - list(): Promise; + list(options: any): Promise; + create(projectId: string|undefined, options: any): Promise; + }; + + apps: { + list(platform: string|undefined, options: any): Promise; + create(platform: string, displayName: string|undefined, options: any): Promise; + sdkconfig(type: string, projectId: string, options: any): Promise; + }; + + hosting: { + sites: { + list(options: any): Promise<{ sites: FirebaseHostingSite[]}>; + create(siteId: string, options: any): Promise; + } }; logger: { - add(...args: any[]): any + // firebase-tools v8 + add: (...args: any[]) => any + // firebase-tools v9 + logger: { + add: (...args: any[]) => any; + } + }; + + cli: { + version(): string; }; - login(): Promise; + login: { + list(): Promise<{user: Record}[] | { users: undefined }>; + add(): Promise>; + use(email: string, options?: unknown): Promise; + } & ((options?: unknown) => Promise>); deploy(config: FirebaseDeployConfig): Promise; serve(options: any): Promise; use(options: any, lol: any): Promise; + + init(feature: string, options: any): Promise; } export interface FirebaseHostingRewrite { @@ -37,16 +144,27 @@ export interface FirebaseHostingRewrite { export interface FirebaseHostingConfig { public?: string; - ignore: string[]; - target: string; - rewrites: FirebaseHostingRewrite[]; + ignore?: string[]; + target?: string; + rewrites?: FirebaseHostingRewrite[]; } -export interface FirebaseFunctionsConfig { [key: string]: any; } +export type FirebaseFunctionsConfig = Record; + +export interface DataConnectConfig { + source?: string; +} + +export interface FirebaseFirestoreConfig { + rules?: string; + indexes?: string; +} export interface FirebaseJSON { hosting?: FirebaseHostingConfig[] | FirebaseHostingConfig; functions?: FirebaseFunctionsConfig; + firestore?: FirebaseFirestoreConfig; + dataconnect?: DataConnectConfig; } export interface FirebaseRcTarget { @@ -55,22 +173,101 @@ export interface FirebaseRcTarget { export interface FirebaseRc { targets?: Record; + projects?: Record; } export interface DeployBuilderSchema { buildTarget?: string; + browserTarget?: string; + firebaseProject?: string; + firebaseHostingSite?: string; preview?: boolean; + target?: boolean; universalBuildTarget?: string; - ssr?: boolean; + serverTarget?: string; + prerenderTarget?: string; + ssr?: boolean | string; + region?: string; + prerender?: boolean; + functionName?: string; + functionsNodeVersion?: number|string; + functionsRuntimeOptions?: HttpsOptions; + cloudRunOptions?: Partial; + outputPath?: string; + CF3v2?: boolean; + version?: number; +} + +export interface CloudRunOptions { + cpus: number; + maxConcurrency: number | 'default'; + maxInstances: number | 'default'; + memory: string; + minInstances: number | 'default'; + timeout: number; + vpcConnector: string; } export interface BuildTarget { name: string; - options?: {[name: string]: any}; + options?: Record; } export interface FSHost { moveSync(src: string, dest: string): void; writeFileSync(src: string, data: string): void; renameSync(src: string, dest: string): void; + copySync(src: string, dest: string): void; + removeSync(src: string): void; + existsSync(src: string): boolean; +} + +export interface WorkspaceProject { + root: string; + sourceRoot?: string; + projectType?: string; + architect?: Record, + configurations?: Record>, + defaultConfiguration?: string, + }>; +} + +export interface Workspace { + defaultProject?: string; + projects: Record; +} + +export interface ConnectorConfig { + location: string; + connector: string; + service: string; +} +export interface ConnectorYaml { + connectorId: string; + generate?: { + javascriptSdk?: { + package: string; + outputDir: string; + packageJsonDir?: string; + angular?: boolean; + } + } +} +export interface DataConnectYaml { + location: string; + serviceId: string; + connectorDirs: string[]; +} +export interface DataConnectConnectorConfig { + connectorYaml: ConnectorYaml; + connectorConfig?: ConnectorConfig; + angular?: boolean; + package?: string; +} + +export interface PackageJson { + dependencies: Record; + devDependencies: Record; } diff --git a/src/schematics/migration.json b/src/schematics/migration.json new file mode 100644 index 000000000..7c040e6a1 --- /dev/null +++ b/src/schematics/migration.json @@ -0,0 +1,20 @@ +{ + "$schema": "../../node_modules/@angular-devkit/schematics/collection-schema.json", + "schematics": { + "migration-v7": { + "version": "7.0.0", + "description": "Update @angular/fire to v7", + "factory": "./update/v7#ngUpdate" + }, + "migration-v21": { + "version": "21.0.0", + "description": "Align the workspace's firebase dependency with the range @angular/fire 21 requires, and rewrite Vertex AI imports to Firebase AI Logic (getVertexAI callers keep the Vertex AI backend)", + "factory": "./update/v21#ngUpdate" + }, + "ng-post-upgate": { + "description": "Print out results after ng-update", + "factory": "./update#ngPostUpdate", + "private": true + } + } +} \ No newline at end of file diff --git a/src/schematics/ng-add-common.ts b/src/schematics/ng-add-common.ts deleted file mode 100644 index 813d146a1..000000000 --- a/src/schematics/ng-add-common.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { SchematicsException, Tree, SchematicContext } from '@angular-devkit/schematics'; -import { FirebaseRc } from './interfaces'; -import * as semver from 'semver'; - -export interface NgAddOptions { - firebaseProject: string; - project?: string; -} - -export interface NgAddNormalizedOptions { - firebaseProject: string; - project: string; -} - -export interface DeployOptions { - project: string; -} - -export const stringifyFormatted = (obj: any) => JSON.stringify(obj, null, 2); - -export const overwriteIfExists = ( - tree: Tree, - path: string, - content: string -) => { - if (tree.exists(path)) { - tree.overwrite(path, content); - } else { - tree.create(path, content); - } -}; - -function emptyFirebaseRc() { - return { - targets: {} - }; -} - -function generateFirebaseRcTarget(firebaseProject: string, project: string) { - return { - hosting: { - [project]: [ - // TODO(kirjs): Generally site name is consistent with the project name, but there are edge cases. - firebaseProject - ] - } - }; -} - -export function generateFirebaseRc( - tree: Tree, - path: string, - firebaseProject: string, - project: string -) { - const firebaseRc: FirebaseRc = tree.exists(path) - ? safeReadJSON(path, tree) - : emptyFirebaseRc(); - - firebaseRc.targets = firebaseRc.targets || {}; - - /* TODO do we want to prompt? - if (firebaseProject in firebaseRc.targets) { - throw new SchematicsException( - `Firebase project ${firebaseProject} already defined in .firebaserc` - ); - }*/ - - firebaseRc.targets[firebaseProject] = generateFirebaseRcTarget( - firebaseProject, - project - ); - - overwriteIfExists(tree, path, stringifyFormatted(firebaseRc)); -} - -export function safeReadJSON(path: string, tree: Tree) { - try { - // tslint:disable-next-line:no-non-null-assertion - return JSON.parse(tree.read(path)!.toString()); - } catch (e) { - throw new SchematicsException(`Error when parsing ${path}: ${e.message}`); - } -} - -export const addDependencies = ( - host: Tree, - deps: { [name: string]: { dev?: boolean, version: string } }, - context: SchematicContext -) => { - const packageJson = - host.exists('package.json') && safeReadJSON('package.json', host); - - if (packageJson === undefined) { - throw new SchematicsException('Could not locate package.json'); - } - - Object.keys(deps).forEach(depName => { - const dep = deps[depName]; - if (dep.dev) { - const existingVersion = packageJson.devDependencies[depName]; - if (existingVersion) { - if (!semver.intersects(existingVersion, dep.version)) { - context.logger.warn(`âš ī¸ The ${depName} devDependency specified in your package.json (${existingVersion}) does not fulfill AngularFire's dependency (${dep.version})`); - // TODO offer to fix - } - } else { - packageJson.devDependencies[depName] = dep.version; - } - } else { - const existingVersion = packageJson.dependencies[depName]; - if (existingVersion) { - if (!semver.intersects(existingVersion, dep.version)) { - context.logger.warn(`âš ī¸ The ${depName} dependency specified in your package.json (${existingVersion}) does not fulfill AngularFire's dependency (${dep.version})`); - // TODO offer to fix - } - } else { - packageJson.dependencies[depName] = dep.version; - } - } - }); - - overwriteIfExists(host, 'package.json', stringifyFormatted(packageJson)); -}; diff --git a/src/schematics/ng-add-ssr.ts b/src/schematics/ng-add-ssr.ts deleted file mode 100644 index 5f2d8d8ad..000000000 --- a/src/schematics/ng-add-ssr.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { SchematicsException, Tree, SchematicContext } from '@angular-devkit/schematics'; -import { experimental } from '@angular-devkit/core'; -import { - addDependencies, - generateFirebaseRc, - NgAddNormalizedOptions, - overwriteIfExists, - safeReadJSON, - stringifyFormatted -} from './ng-add-common'; -import { FirebaseJSON } from './interfaces'; - -import { default as defaultDependencies, firebaseFunctions as firebaseFunctionsDependencies } from './versions.json'; -import { dirname, join } from 'path'; - -// We consider a project to be a universal project if it has a `server` architect -// target. If it does, it knows how to build the application's server. -export const isUniversalApp = ( - project: experimental.workspace.WorkspaceProject -) => project.architect && project.architect.server; - -function emptyFirebaseJson(source: string) { - return { - hosting: [], - functions: { - source - } - }; -} - -function generateHostingConfig(project: string, dist: string) { - return { - target: project, - public: join(dirname(dist), dist), - ignore: ['**/.*'], - headers: [{ - source: '*.[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].+(css|js)', - headers: [{ - key: 'Cache-Control', - value: 'public,max-age=31536000,immutable' - }] - }], - rewrites: [ - { - source: '**', - function: 'ssr' - } - ] - }; -} - -function generateFunctionsConfig(dist: string) { - return { - source: dirname(dist) - }; -} - -export function generateFirebaseJson( - tree: Tree, - path: string, - project: string, - dist: string, - serverOutput: string -) { - const firebaseJson: FirebaseJSON = tree.exists(path) - ? safeReadJSON(path, tree) - : emptyFirebaseJson(dirname(serverOutput)); - - /* TODO do we want to prompt for override? - if ( - firebaseJson.hosting && - ((Array.isArray(firebaseJson.hosting) && - firebaseJson.hosting.find(config => config.target === project)) || - (firebaseJson.hosting).target === project) - ) { - throw new SchematicsException( - `Target ${project} already exists in firebase.json` - ); - }*/ - - const newConfig = generateHostingConfig(project, dist); - if (firebaseJson.hosting === undefined) { - firebaseJson.hosting = newConfig; - } else if (Array.isArray(firebaseJson.hosting)) { - const existingConfigIndex = firebaseJson.hosting.findIndex(config => config.target === newConfig.target); - if (existingConfigIndex > -1) { - firebaseJson.hosting.splice(existingConfigIndex, 1, newConfig); - } else { - firebaseJson.hosting.push(newConfig); - } - } else { - firebaseJson.hosting = [firebaseJson.hosting, newConfig]; - } - - firebaseJson.functions = generateFunctionsConfig(dist); - - overwriteIfExists(tree, path, stringifyFormatted(firebaseJson)); -} - -export const addFirebaseFunctionsDependencies = (tree: Tree, context: SchematicContext) => { - addDependencies( - tree, - {...defaultDependencies, ...firebaseFunctionsDependencies}, - context - ); -}; - -export const setupUniversalDeployment = (config: { - project: experimental.workspace.WorkspaceProject; - options: NgAddNormalizedOptions; - workspacePath: string; - workspace: experimental.workspace.WorkspaceSchema; - tree: Tree; -}) => { - const { tree, workspacePath, workspace, options } = config; - const project = workspace.projects[options.project]; - - if ( - !project.architect || - !project.architect.build || - !project.architect.build.options || - !project.architect.build.options.outputPath - ) { - throw new SchematicsException( - `Cannot read the output path (architect.build.options.outputPath) of the Angular project "${options.project}" in angular.json` - ); - } - - if ( - !project.architect || - !project.architect.server || - !project.architect.server.options || - !project.architect.server.options.outputPath - ) { - throw new SchematicsException( - `Cannot read the output path (architect.server.options.outputPath) of the Angular project "${options.project}" in angular.json` - ); - } - - const staticOutput = project.architect.build.options.outputPath; - const serverOutput = project.architect.server.options.outputPath; - - // Add firebase libraries to externalDependencies. For older versions of @firebase/firestore grpc native would cause issues when - // bundled. While, it's using grpc-js now and doesn't have issues, ngcc tends to bundle the esm version of the libraries; which - // is problematic for SSR (references to Window, etc.) Let's just mark all of them as external so we know the CJS is used. - const externalDependencies: string[] = project.architect.server.options.externalDependencies || []; - [ - 'firebase', - '@firebase/app', - '@firebase/analytics', - '@firebase/app', - '@firebase/auth', - '@firebase/component', - '@firebase/database', - '@firebase/firestore', - '@firebase/functions', - '@firebase/installations', - '@firebase/messaging', - '@firebase/storage', - '@firebase/performance', - '@firebase/remote-config', - '@firebase/util' - ].forEach(dep => { - if (!externalDependencies.includes(dep)) { externalDependencies.push(dep); } - }); - - project.architect.server.options.externalDependencies = externalDependencies; - - project.architect.deploy = { - builder: '@angular/fire:deploy', - options: { - ssr: true - } - }; - - tree.overwrite(workspacePath, JSON.stringify(workspace, null, 2)); - - generateFirebaseJson(tree, 'firebase.json', options.project, staticOutput, serverOutput); - generateFirebaseRc( - tree, - '.firebaserc', - options.firebaseProject, - options.project - ); - - return tree; -}; diff --git a/src/schematics/ng-add-static.ts b/src/schematics/ng-add-static.ts deleted file mode 100644 index 3c6ff645a..000000000 --- a/src/schematics/ng-add-static.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { SchematicsException, Tree, SchematicContext } from '@angular-devkit/schematics'; -import { experimental } from '@angular-devkit/core'; -import { - addDependencies, - generateFirebaseRc, - NgAddNormalizedOptions, - overwriteIfExists, - safeReadJSON, - stringifyFormatted -} from './ng-add-common'; -import { FirebaseJSON } from './interfaces'; - -import { default as defaultDependencies } from './versions.json'; - -function emptyFirebaseJson() { - return { - hosting: [] - }; -} - -function generateHostingConfig(project: string, dist: string) { - return { - target: project, - public: dist, - ignore: ['**/.*'], - headers: [{ - source: '*.[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].+(css|js)', - headers: [{ - key: 'Cache-Control', - value: 'public,max-age=31536000,immutable' - }] - }], - rewrites: [ - { - source: '**', - destination: '/index.html' - } - ] - }; -} - -export function generateFirebaseJson( - tree: Tree, - path: string, - project: string, - dist: string -) { - const firebaseJson: FirebaseJSON = tree.exists(path) - ? safeReadJSON(path, tree) - : emptyFirebaseJson(); - - /* TODO do we want to prompt for override? - if ( - firebaseJson.hosting && - ((Array.isArray(firebaseJson.hosting) && - firebaseJson.hosting.find(config => config.target === project)) || - (firebaseJson.hosting as FirebaseHostingConfig).target === project) - ) { - throw new SchematicsException( - `Target ${project} already exists in firebase.json` - ); - }*/ - - const newConfig = generateHostingConfig(project, dist); - if (firebaseJson.hosting === undefined) { - firebaseJson.hosting = newConfig; - } else if (Array.isArray(firebaseJson.hosting)) { - firebaseJson.hosting.push(newConfig); - } else { - firebaseJson.hosting = [firebaseJson.hosting, newConfig]; - } - - overwriteIfExists(tree, path, stringifyFormatted(firebaseJson)); -} - -export const addFirebaseHostingDependencies = (tree: Tree, context: SchematicContext) => { - addDependencies( - tree, - defaultDependencies, - context - ); -}; - -export const setupStaticDeployment = (config: { - project: experimental.workspace.WorkspaceProject; - options: NgAddNormalizedOptions; - workspacePath: string; - workspace: experimental.workspace.WorkspaceSchema; - tree: Tree; -}) => { - const { tree, workspacePath, workspace, options } = config; - const project = workspace.projects[options.project]; - - if ( - !project.architect || - !project.architect.build || - !project.architect.build.options || - !project.architect.build.options.outputPath - ) { - throw new SchematicsException( - `Cannot read the output path (architect.build.options.outputPath) of the Angular project "${options.project}" in angular.json` - ); - } - - const outputPath = project.architect.build.options.outputPath; - - project.architect.deploy = { - builder: '@angular/fire:deploy', - options: {} - }; - - tree.overwrite(workspacePath, JSON.stringify(workspace, null, 2)); - generateFirebaseJson(tree, 'firebase.json', options.project, outputPath); - generateFirebaseRc( - tree, - '.firebaserc', - options.firebaseProject, - options.project - ); - - return tree; -}; diff --git a/src/schematics/ng-add.jasmine.ts b/src/schematics/ng-add.jasmine.ts deleted file mode 100644 index 0f6c66b4b..000000000 --- a/src/schematics/ng-add.jasmine.ts +++ /dev/null @@ -1,618 +0,0 @@ -import { Tree } from '@angular-devkit/schematics'; -import { setupProject } from './ng-add'; -import 'jasmine'; - -const PROJECT_NAME = 'pie-ka-chu'; -const PROJECT_ROOT = 'pirojok'; -const FIREBASE_PROJECT = 'pirojok-111e3'; -const OTHER_PROJECT_NAME = 'pi-catch-you'; -const OTHER_FIREBASE_PROJECT_NAME = 'bi-catch-you-77e7e'; - -function generateAngularJson() { - return { - defaultProject: PROJECT_NAME, - projects: { - [PROJECT_NAME]: { - projectType: 'application', - root: PROJECT_ROOT, - architect: { - build: { - options: { - outputPath: 'dist/ikachu' - } - } - } - }, - [OTHER_PROJECT_NAME]: { - projectType: 'application', - root: PROJECT_ROOT, - architect: { - build: { - options: { - outputPath: 'dist/ikachu' - } - } - } - } - } - }; -} - -function generateAngularJsonWithServer() { - return { - defaultProject: PROJECT_NAME, - projects: { - [PROJECT_NAME]: { - projectType: 'application', - root: PROJECT_ROOT, - architect: { - build: { - options: { - outputPath: 'dist/ikachu' - } - }, - server: { - options: { - outputPath: 'dist/server' - } - } - } - }, - [OTHER_PROJECT_NAME]: { - projectType: 'application', - root: PROJECT_ROOT, - architect: { - build: { - options: { - outputPath: 'dist/ikachu' - } - }, - server: { - options: { - outputPath: 'dist/server' - } - } - } - } - } - }; -} - -const initialFirebaseJson = `{ - \"hosting\": [ - { - \"target\": \"pie-ka-chu\", - \"public\": \"dist/ikachu\", - \"ignore\": [ - \"**/.*\" - ], - \"headers\": [ - { - \"source\": \"*.[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].+(css|js)\", - \"headers\": [ - { - \"key\": \"Cache-Control\", - \"value\": \"public,max-age=31536000,immutable\" - } - ] - } - ], - \"rewrites\": [ - { - \"source\": \"**\", - \"destination\": \"/index.html\" - } - ] - } - ] -}`; - -const initialFirebaserc = `{ - \"targets\": { - \"pirojok-111e3\": { - \"hosting\": { - \"pie-ka-chu\": [ - \"pirojok-111e3\" - ] - } - } - } -}`; - -const initialAngularJson = `{ - \"defaultProject\": \"pie-ka-chu\", - \"projects\": { - \"pie-ka-chu\": { - \"projectType\": \"application\", - \"root\": \"pirojok\", - \"architect\": { - \"build\": { - \"options\": { - \"outputPath\": \"dist/ikachu\" - } - }, - \"deploy\": { - \"builder\": \"@angular/fire:deploy\", - \"options\": {} - } - } - }, - \"pi-catch-you\": { - \"projectType\": \"application\", - \"root\": \"pirojok\", - \"architect\": { - \"build\": { - \"options\": { - \"outputPath\": \"dist/ikachu\" - } - } - } - } - } -}`; - -const overwriteFirebaseJson = `{ - \"hosting\": [ - { - \"target\": \"pie-ka-chu\", - \"public\": \"dist/ikachu\", - \"ignore\": [ - \"**/.*\" - ], - \"headers\": [ - { - \"source\": \"*.[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].+(css|js)\", - \"headers\": [ - { - \"key\": \"Cache-Control\", - \"value\": \"public,max-age=31536000,immutable\" - } - ] - } - ], - \"rewrites\": [ - { - \"source\": \"**\", - \"destination\": \"/index.html\" - } - ] - } - ] -}`; - -const overwriteFirebaserc = `{ - \"targets\": { - \"pirojok-111e3\": { - \"hosting\": { - \"pie-ka-chu\": [ - \"pirojok-111e3\" - ] - } - } - } -}`; - -const overwriteAngularJson = `{ - \"defaultProject\": \"pie-ka-chu\", - \"projects\": { - \"pie-ka-chu\": { - \"projectType\": \"application\", - \"root\": \"pirojok\", - \"architect\": { - \"build\": { - \"options\": { - \"outputPath\": \"dist/ikachu\" - } - }, - \"deploy\": { - \"builder\": \"@angular/fire:deploy\", - \"options\": {} - } - } - }, - \"pi-catch-you\": { - \"projectType\": \"application\", - \"root\": \"pirojok\", - \"architect\": { - \"build\": { - \"options\": { - \"outputPath\": \"dist/ikachu\" - } - } - } - } - } -}`; - -const projectFirebaseJson = `{ - \"hosting\": [ - { - \"target\": \"pie-ka-chu\", - \"public\": \"dist/ikachu\", - \"ignore\": [ - \"**/.*\" - ], - \"headers\": [ - { - \"source\": \"*.[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].+(css|js)\", - \"headers\": [ - { - \"key\": \"Cache-Control\", - \"value\": \"public,max-age=31536000,immutable\" - } - ] - } - ], - \"rewrites\": [ - { - \"source\": \"**\", - \"destination\": \"/index.html\" - } - ] - }, - { - \"target\": \"pi-catch-you\", - \"public\": \"dist/ikachu\", - \"ignore\": [ - \"**/.*\" - ], - \"headers\": [ - { - \"source\": \"*.[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].+(css|js)\", - \"headers\": [ - { - \"key\": \"Cache-Control\", - \"value\": \"public,max-age=31536000,immutable\" - } - ] - } - ], - \"rewrites\": [ - { - \"source\": \"**\", - \"destination\": \"/index.html\" - } - ] - } - ] -}`; - -const projectFirebaserc = `{ - \"targets\": { - \"pirojok-111e3\": { - \"hosting\": { - \"pie-ka-chu\": [ - \"pirojok-111e3\" - ] - } - }, - \"bi-catch-you-77e7e\": { - \"hosting\": { - \"pi-catch-you\": [ - \"bi-catch-you-77e7e\" - ] - } - } - } -}`; - -const projectAngularJson = `{ - \"defaultProject\": \"pie-ka-chu\", - \"projects\": { - \"pie-ka-chu\": { - \"projectType\": \"application\", - \"root\": \"pirojok\", - \"architect\": { - \"build\": { - \"options\": { - \"outputPath\": \"dist/ikachu\" - } - }, - \"deploy\": { - \"builder\": \"@angular/fire:deploy\", - \"options\": {} - } - } - }, - \"pi-catch-you\": { - \"projectType\": \"application\", - \"root\": \"pirojok\", - \"architect\": { - \"build\": { - \"options\": { - \"outputPath\": \"dist/ikachu\" - } - }, - \"deploy\": { - \"builder\": \"@angular/fire:deploy\", - \"options\": {} - } - } - } - } -}`; - -const universalFirebaseJson = { - hosting: [{ - target: 'pie-ka-chu', - public: 'dist/dist/ikachu', - ignore: [ - '**/.*' - ], - headers: [{ - source: '*.[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].+(css|js)', - headers: [{ - key: 'Cache-Control', - value: 'public,max-age=31536000,immutable' - }] - }], - rewrites: [ - { - source: '**', - function: 'ssr' - } - ] - }], - functions: { - source: 'dist' - } -}; - -describe('ng-add', () => { - describe('generating files', () => { - let tree: Tree; - - beforeEach(() => { - tree = Tree.empty(); - tree.create('angular.json', JSON.stringify(generateAngularJson())); - }); - - it('generates new files if starting from scratch', async () => { - const result = await setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - isUniversalProject: false, - project: PROJECT_NAME - }); - expect(result.read('firebase.json').toString()).toEqual(initialFirebaseJson); - expect(result.read('.firebaserc').toString()).toEqual(initialFirebaserc); - expect(result.read('angular.json').toString()).toEqual(initialAngularJson); - }); - - it('uses default project', async () => { - const result = await setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - isUniversalProject: false, - project: undefined - }); - expect(result.read('firebase.json').toString()).toEqual(overwriteFirebaseJson); - expect(result.read('.firebaserc').toString()).toEqual(overwriteFirebaserc); - expect(result.read('angular.json').toString()).toEqual(overwriteAngularJson); - }); - - it('overrides existing files', async () => { - const tempTree = await setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - isUniversalProject: false, project: PROJECT_NAME - }); - const result = await setupProject(tempTree, { - firebaseProject: OTHER_FIREBASE_PROJECT_NAME, - project: OTHER_PROJECT_NAME, - isUniversalProject: false - }); - expect(result.read('firebase.json').toString()).toEqual(projectFirebaseJson); - expect(result.read('.firebaserc').toString()).toEqual(projectFirebaserc); - expect(result.read('angular.json').toString()).toEqual(projectAngularJson); - }); - }); - - describe('error handling', () => { - it('fails if project not defined', () => { - const tree = Tree.empty(); - const angularJSON = generateAngularJson(); - delete angularJSON.defaultProject; - tree.create('angular.json', JSON.stringify(angularJSON)); - expect(() => - setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - isUniversalProject: false, - project: undefined - }) - ).toThrowError( - /No Angular project selected and no default project in the workspace/ - ); - }); - - it('Should throw if angular.json not found', async () => { - expect(() => - setupProject(Tree.empty(), { - firebaseProject: FIREBASE_PROJECT, - isUniversalProject: false, - project: PROJECT_NAME - }) - ).toThrowError(/Could not find angular.json/); - }); - - it('Should throw if angular.json can not be parsed', async () => { - const tree = Tree.empty(); - tree.create('angular.json', 'hi'); - expect(() => - setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - isUniversalProject: false, - project: PROJECT_NAME - }) - ).toThrowError(/Could not parse angular.json/); - }); - - it('Should throw if specified project does not exist ', async () => { - const tree = Tree.empty(); - tree.create('angular.json', JSON.stringify({ projects: {} })); - expect(() => - setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - isUniversalProject: false, - project: PROJECT_NAME - }) - ).toThrowError(/The specified Angular project is not defined in this workspace/); - }); - - it('Should throw if specified project is not application', async () => { - const tree = Tree.empty(); - tree.create( - 'angular.json', - JSON.stringify({ - projects: { [PROJECT_NAME]: { projectType: 'pokemon' } } - }) - ); - expect(() => - setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - isUniversalProject: false, - project: PROJECT_NAME - }) - ).toThrowError(/Deploy requires an Angular project type of "application" in angular.json/); - }); - - it('Should throw if app does not have architect configured', async () => { - const tree = Tree.empty(); - tree.create( - 'angular.json', - JSON.stringify({ - projects: { [PROJECT_NAME]: { projectType: 'application' } } - }) - ); - expect(() => - setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - isUniversalProject: false, - project: PROJECT_NAME - }) - ).toThrowError(/Cannot read the output path/); - }); - - /* TODO do something other than throw - it('Should throw if firebase.json has the project already', async () => { - const tree = Tree.empty(); - tree.create('angular.json', JSON.stringify(generateAngularJson())); - const tempTree = await setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - isUniversalProject: false, - project: PROJECT_NAME - }); - - expect(() => - setupProject(tempTree, { - firebaseProject: FIREBASE_PROJECT, - isUniversalProject: false, - project: PROJECT_NAME - }) - ).toThrowError(/already exists in firebase.json/); - }); - - it('Should throw if firebase.json is broken', async () => { - const tree = Tree.empty(); - tree.create('angular.json', JSON.stringify(generateAngularJson())); - tree.create('firebase.json', `I'm broken 😔`); - expect(() => - setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - isUniversalProject: false, - project: PROJECT_NAME - }) - ).toThrowError(/firebase.json: Unexpected token/); - });*/ - - it('Should throw if .firebaserc is broken', async () => { - const tree = Tree.empty(); - tree.create('angular.json', JSON.stringify(generateAngularJson())); - tree.create('.firebaserc', `I'm broken 😔`); - expect(() => - setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - isUniversalProject: false, - project: PROJECT_NAME - }) - ).toThrowError(/.firebaserc: Unexpected token/); - }); - - /* TODO do something else - - it('Should throw if firebase.json has the project already', async () => { - const tree = Tree.empty(); - tree.create('angular.json', JSON.stringify(generateAngularJson())); - const tempTree = await setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - isUniversalProject: false, - project: PROJECT_NAME - }); - - expect(() => - setupProject(tempTree, { - firebaseProject: FIREBASE_PROJECT, - isUniversalProject: false, - project: OTHER_PROJECT_NAME - }) - ).toThrowError(/ already defined in .firebaserc/); - }); - - it('Should throw if firebase.json is broken', async () => { - const tree = Tree.empty(); - tree.create('angular.json', JSON.stringify(generateAngularJson())); - - const tempTree = await setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - isUniversalProject: false, - project: PROJECT_NAME - }); - - expect(() => - setupProject(tempTree, { - firebaseProject: FIREBASE_PROJECT, - isUniversalProject: false, - project: OTHER_PROJECT_NAME - }) - ).toThrowError(/ already defined in .firebaserc/); - }); */ - - describe('universal app', () => { - it('should fail without a server project', async () => { - const tree = Tree.empty(); - tree.create('angular.json', JSON.stringify(generateAngularJson())); - - expect(() => setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - isUniversalProject: true, - project: PROJECT_NAME - })).toThrowError(/\(architect.server.options.outputPath\) of the Angular project "pie-ka-chu" in angular.json/); - }); - - it('should add a @angular/fire builder', async () => { - const tree = Tree.empty(); - tree.create('angular.json', JSON.stringify(generateAngularJsonWithServer())); - - const result = await setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - isUniversalProject: true, - project: PROJECT_NAME - }); - - const workspace = JSON.parse((await result.read('angular.json')).toString()); - expect(workspace.projects['pie-ka-chu'].architect.deploy.options.ssr).toBeTrue(); - }); - - it('should configure firebase.json', async () => { - const tree = Tree.empty(); - tree.create('angular.json', JSON.stringify(generateAngularJsonWithServer())); - - const result = await setupProject(tree, { - firebaseProject: FIREBASE_PROJECT, - isUniversalProject: true, - project: PROJECT_NAME - }); - - const firebaseJson = JSON.parse((await result.read('firebase.json')).toString()); - expect(firebaseJson).toEqual(universalFirebaseJson); - }); - }); - }); -}); diff --git a/src/schematics/ng-add.ts b/src/schematics/ng-add.ts deleted file mode 100644 index c06b8e673..000000000 --- a/src/schematics/ng-add.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { SchematicContext, SchematicsException, Tree } from '@angular-devkit/schematics'; -import { NodePackageInstallTask, RunSchematicTask } from '@angular-devkit/schematics/tasks'; -import { experimental, JsonParseMode, parseJson } from '@angular-devkit/core'; -import { listProjects, projectPrompt, projectTypePrompt } from './utils'; - -import { DeployOptions, NgAddNormalizedOptions } from './ng-add-common'; -import { addFirebaseFunctionsDependencies, setupUniversalDeployment } from './ng-add-ssr'; -import { addFirebaseHostingDependencies, setupStaticDeployment } from './ng-add-static'; - -function getWorkspace( - host: Tree -): { path: string; workspace: experimental.workspace.WorkspaceSchema } { - const possibleFiles = ['/angular.json', '/.angular.json']; - const path = possibleFiles.filter(p => host.exists(p))[0]; - - const configBuffer = host.read(path); - if (configBuffer === null) { - throw new SchematicsException(`Could not find angular.json`); - } - const content = configBuffer.toString(); - - let workspace: experimental.workspace.WorkspaceSchema; - try { - workspace = (parseJson( - content, - JsonParseMode.Loose - ) as {}) as experimental.workspace.WorkspaceSchema; - } catch (e) { - throw new SchematicsException(`Could not parse angular.json: ` + e.message); - } - - return { - path, - workspace - }; -} - -const getProject = (options: DeployOptions, host: Tree) => { - const { workspace } = getWorkspace(host); - const projectName = options.project || workspace.defaultProject; - - if (!projectName) { - throw new SchematicsException( - 'No Angular project selected and no default project in the workspace' - ); - } - - const project = workspace.projects[projectName]; - if (!project) { - throw new SchematicsException( - 'The specified Angular project is not defined in this workspace' - ); - } - - if (project.projectType !== 'application') { - throw new SchematicsException( - `Deploy requires an Angular project type of "application" in angular.json` - ); - } - - return {project, projectName}; -}; - -export const setupProject = - (host: Tree, options: DeployOptions & { isUniversalProject: boolean, firebaseProject: string }) => { - const { path: workspacePath, workspace } = getWorkspace(host); - - const {project, projectName} = getProject(options, host); - - const config: NgAddNormalizedOptions = { - project: projectName, - firebaseProject: options.firebaseProject - }; - - if (options.isUniversalProject) { - return setupUniversalDeployment({ - workspace, - workspacePath, - options: config, - tree: host, - project - }); - } - return setupStaticDeployment({ - workspace, - workspacePath, - options: config, - tree: host, - project - }); - }; - -export const ngAddSetupProject = ( - options: DeployOptions & { isUniversalProject: boolean } -) => async (host: Tree) => { - const projects = await listProjects(); - const { firebaseProject } = await projectPrompt(projects); - return setupProject(host, {...options, firebaseProject}); -}; - -export const ngAdd = (options: DeployOptions) => ( - host: Tree, - context: SchematicContext -) => { - - const {project} = getProject(options, host); - - return projectTypePrompt(project).then( - ({ universalProject }: { universalProject: boolean }) => { - if (universalProject) { - addFirebaseFunctionsDependencies(host, context); - } else { - addFirebaseHostingDependencies(host, context); - } - const projectOptions: DeployOptions & { isUniversalProject: boolean } = { - ...options, - isUniversalProject: universalProject - }; - context.addTask(new RunSchematicTask('ng-add-setup-project', projectOptions), [ - context.addTask(new NodePackageInstallTask()) - ]); - } - ); -}; diff --git a/src/schematics/ngcc-config.ts b/src/schematics/ngcc-config.ts deleted file mode 100644 index 9b6792e41..000000000 --- a/src/schematics/ngcc-config.ts +++ /dev/null @@ -1,70 +0,0 @@ -export const packages = { - firebase: { - entryPoints: { - '.': { override: { main: undefined, browser: undefined } }, - './analytics': { override: { main: undefined, browser: undefined } }, - './app': {override: { main: undefined, browser: undefined } }, - './auth': {override: { main: undefined, browser: undefined } }, - './database': { override: { main: undefined, browser: undefined } }, - './firestore': { override: { main: undefined, browser: undefined } }, - './functions': { override: { main: undefined, browser: undefined } }, - './installations': { override: { main: undefined, browser: undefined } }, - './storage': { override: { main: undefined, browser: undefined } }, - './performance': { override: { main: undefined, browser: undefined } }, - './remote-config': { override: { main: undefined, browser: undefined } }, - }, - generateDeepReexports: true - }, - '@firebase/analytics': { - entryPoints: { '.': { override : { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/app': { - entryPoints: { '.': { override : { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/auth': { - entryPoints: { '.': { override : { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/component': { - entryPoints: { '.': { override: { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/database': { - entryPoints: { '.': { override: { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/firestore': { - entryPoints: { '.': { override: { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/functions': { - entryPoints: { '.': { override: { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/installations': { - entryPoints: { '.': { override: { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/messaging': { - entryPoints: { '.': { override: { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/storage': { - entryPoints: { '.': { override: { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/performance': { - entryPoints: { '.': { override: { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/remote-config': { - entryPoints: { '.': { override: { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - }, - '@firebase/util': { - entryPoints: { '.': { override: { main: undefined, browser: undefined } } }, - ignorableDeepImportMatchers: [ /@firebase\/app-types\/private/ ] - } -}; diff --git a/src/schematics/public_api.ts b/src/schematics/public_api.ts deleted file mode 100644 index ad655b255..000000000 --- a/src/schematics/public_api.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './ng-add'; -export * from './deploy/actions'; -export * from './deploy/builder'; diff --git a/src/schematics/setup/firebaseConfigs.jasmine.ts b/src/schematics/setup/firebaseConfigs.jasmine.ts new file mode 100644 index 000000000..88f185e52 --- /dev/null +++ b/src/schematics/setup/firebaseConfigs.jasmine.ts @@ -0,0 +1,176 @@ +import { mkdtempSync, readFileSync, writeFileSync } from 'fs'; +import { tmpdir } from 'os'; +import { join } from 'path'; +import { logging } from '@angular-devkit/core'; +import { HostTree, SchematicContext } from '@angular-devkit/schematics'; +import fsExtra from 'fs-extra'; +import { FEATURES } from '../interfaces.js'; +import { + addFirestoreToFirebaseJson, + createFirestoreStarterFiles, + setDefaultProjectInFirebaseRc, +} from './firebaseConfigs.js'; +import 'jasmine'; + +const context = { logger: new logging.Logger('test') } as unknown as SchematicContext; + +describe('setDefaultProjectInFirebaseRc', () => { + let projectRoot: string; + + beforeEach(() => { + projectRoot = mkdtempSync(join(tmpdir(), 'angularfire-test-')); + }); + + afterEach(() => { + fsExtra.removeSync(projectRoot); + }); + + const firebaseRcOnDisk = () => + JSON.parse(readFileSync(join(projectRoot, '.firebaserc')).toString()); + + it('creates .firebaserc recording the selected project as default', () => { + setDefaultProjectInFirebaseRc(projectRoot, 'my-project'); + expect(firebaseRcOnDisk()).toEqual({ + projects: { default: 'my-project' }, + }); + }); + + it('merges into an existing .firebaserc, preserving targets and other aliases', () => { + writeFileSync(join(projectRoot, '.firebaserc'), JSON.stringify({ + projects: { default: 'old-project', staging: 'staging-project' }, + targets: { 'old-project': { hosting: { app: ['app-site'] } } }, + })); + setDefaultProjectInFirebaseRc(projectRoot, 'new-project'); + expect(firebaseRcOnDisk()).toEqual({ + projects: { default: 'new-project', staging: 'staging-project' }, + targets: { 'old-project': { hosting: { app: ['app-site'] } } }, + }); + }); + + it('names the file when an existing .firebaserc cannot be parsed', () => { + writeFileSync(join(projectRoot, '.firebaserc'), '{ not json'); + expect(() => setDefaultProjectInFirebaseRc(projectRoot, 'my-project')) + .toThrowError(/\.firebaserc/); + }); + +}); + +describe('createFirestoreStarterFiles', () => { + + it('creates test-mode rules and an empty index manifest when Firestore is selected', () => { + const tree = new HostTree(); + createFirestoreStarterFiles(tree, context, [FEATURES.Firestore]); + const rules = tree.readText('/firestore.rules'); + expect(rules).toContain("rules_version='2'"); + expect(rules).toContain('allow read, write: if request.time < timestamp.date('); + expect(JSON.parse(tree.readText('/firestore.indexes.json'))).toEqual({ + indexes: [], + fieldOverrides: [], + }); + }); + + it('sets the rules to expire roughly 30 days out', () => { + const tree = new HostTree(); + createFirestoreStarterFiles(tree, context, [FEATURES.Firestore]); + const match = /timestamp\.date\((\d+), (\d+), (\d+)\)/.exec(tree.readText('/firestore.rules')); + expect(match).not.toBeNull(); + const [, year, month, day] = match.map(Number); + const expiry = new Date(year, month - 1, day); + const daysOut = (expiry.getTime() - Date.now()) / (1000 * 60 * 60 * 24); + expect(daysOut).toBeGreaterThan(28); + expect(daysOut).toBeLessThan(31); + }); + + it('never overwrites an existing rules file', () => { + const tree = new HostTree(); + tree.create('/firestore.rules', 'my existing rules'); + createFirestoreStarterFiles(tree, context, [FEATURES.Firestore]); + expect(tree.readText('/firestore.rules')).toBe('my existing rules'); + }); + + it('still creates the index manifest when only the rules file exists', () => { + const tree = new HostTree(); + tree.create('/firestore.rules', 'my existing rules'); + createFirestoreStarterFiles(tree, context, [FEATURES.Firestore]); + expect(tree.exists('/firestore.indexes.json')).toBeTrue(); + }); + + it('does nothing when Firestore is not selected', () => { + const tree = new HostTree(); + createFirestoreStarterFiles(tree, context, [FEATURES.Authentication]); + expect(tree.exists('/firestore.rules')).toBeFalse(); + expect(tree.exists('/firestore.indexes.json')).toBeFalse(); + }); + + it('creates nothing when firebase.json already has a firestore section', () => { + const tree = new HostTree(); + createFirestoreStarterFiles(tree, context, [FEATURES.Firestore], { + firestore: { rules: 'custom.rules' }, + }); + expect(tree.exists('/firestore.rules')).toBeFalse(); + expect(tree.exists('/firestore.indexes.json')).toBeFalse(); + }); + +}); + +describe('addFirestoreToFirebaseJson', () => { + let projectRoot: string; + + beforeEach(() => { + projectRoot = mkdtempSync(join(tmpdir(), 'angularfire-test-')); + }); + + afterEach(() => { + fsExtra.removeSync(projectRoot); + }); + + const firebaseJsonOnDisk = () => + JSON.parse(readFileSync(join(projectRoot, 'firebase.json')).toString()); + + it('adds the firestore section pointing at the starter files', () => { + writeFileSync(join(projectRoot, 'firebase.json'), '{}'); + addFirestoreToFirebaseJson(projectRoot, context, [FEATURES.Firestore]); + expect(firebaseJsonOnDisk().firestore).toEqual({ + rules: 'firestore.rules', + indexes: 'firestore.indexes.json', + }); + }); + + it('preserves sections other tools wrote to firebase.json', () => { + writeFileSync(join(projectRoot, 'firebase.json'), JSON.stringify({ + dataconnect: { source: 'dataconnect' }, + })); + addFirestoreToFirebaseJson(projectRoot, context, [FEATURES.Firestore]); + expect(firebaseJsonOnDisk().dataconnect).toEqual({ source: 'dataconnect' }); + expect(firebaseJsonOnDisk().firestore).toBeDefined(); + }); + + it('leaves an existing firestore section untouched', () => { + writeFileSync(join(projectRoot, 'firebase.json'), JSON.stringify({ + firestore: { rules: 'custom.rules' }, + })); + addFirestoreToFirebaseJson(projectRoot, context, [FEATURES.Firestore]); + expect(firebaseJsonOnDisk().firestore).toEqual({ rules: 'custom.rules' }); + }); + + it('does nothing when Firestore is not selected', () => { + writeFileSync(join(projectRoot, 'firebase.json'), '{}'); + addFirestoreToFirebaseJson(projectRoot, context, [FEATURES.Authentication]); + expect(firebaseJsonOnDisk().firestore).toBeUndefined(); + }); + + it('warns and skips when firebase.json cannot be read', () => { + const warnSpy = spyOn(context.logger, 'warn'); + addFirestoreToFirebaseJson(projectRoot, context, [FEATURES.Firestore]); + expect(warnSpy).toHaveBeenCalled(); + }); + + it('warns and skips when firebase.json parses to a non-object', () => { + writeFileSync(join(projectRoot, 'firebase.json'), 'null'); + const warnSpy = spyOn(context.logger, 'warn'); + addFirestoreToFirebaseJson(projectRoot, context, [FEATURES.Firestore]); + expect(warnSpy).toHaveBeenCalled(); + expect(readFileSync(join(projectRoot, 'firebase.json')).toString()).toBe('null'); + }); + +}); diff --git a/src/schematics/setup/firebaseConfigs.ts b/src/schematics/setup/firebaseConfigs.ts new file mode 100644 index 000000000..b08838c83 --- /dev/null +++ b/src/schematics/setup/firebaseConfigs.ts @@ -0,0 +1,128 @@ +import { existsSync, readFileSync, writeFileSync } from 'fs'; +import { join } from 'path'; +import { SchematicContext, SchematicsException, Tree } from '@angular-devkit/schematics'; +import { stringifyFormatted } from '../common.js'; +import { FEATURES, FirebaseJSON, FirebaseRc } from '../interfaces.js'; + +/** + * Builds the same test-mode starter rules `firebase init firestore` generates: anyone can read + * and write until a date 30 days from generation, after which all client requests are denied. + */ +const testModeFirestoreRules = () => { + const expiry = new Date(); + expiry.setDate(expiry.getDate() + 30); + return `rules_version='2' + +service cloud.firestore { + match /databases/{database}/documents { + match /{document=**} { + // This rule allows anyone with your database reference to view, edit, + // and delete all data in your database. It is useful for getting + // started, but it is configured to expire after 30 days because it + // leaves your app open to attackers. At that time, all client + // requests to your database will be denied. + // + // Make sure to write security rules for your app before that time, or + // else all client requests to your database will be denied until you + // update your rules. + allow read, write: if request.time < timestamp.date(${expiry.getFullYear()}, ${expiry.getMonth() + 1}, ${expiry.getDate()}); + } + } +} +`; +}; + +/** + * Records the selected Firebase project as the workspace's default in `.firebaserc`, so + * firebase-tools commands the user runs after setup completes don't prompt for (or fail without) + * a project. Merges into an existing file — only `projects.default` is set; targets and other + * aliases are preserved. Written to the real filesystem (not the schematic Tree) because + * `firebaseTools.init` writes `.firebaserc` on disk during the same `ng add` run — a Tree-staged + * copy would collide with it when the schematic commits; call this after the last + * `firebaseTools.init` call for that reason. + */ +export const setDefaultProjectInFirebaseRc = (projectRoot: string, projectId: string) => { + const path = join(projectRoot, '.firebaserc'); + let rc: FirebaseRc = {}; + if (existsSync(path)) { + try { + rc = JSON.parse(readFileSync(path).toString()); + } catch (e) { + throw new SchematicsException(`Error when parsing ${path}: ${e.message}`); + } + } + rc.projects = { ...rc.projects, default: projectId }; + writeFileSync(path, stringifyFormatted(rc)); +}; + +/** + * When Firestore is selected, generates `firestore.rules` (test mode, with a logged warning + * about the 30-day expiry) and `firestore.indexes.json`. Existing files are never overwritten — + * a later `firebase deploy` would replace rules already deployed from elsewhere. A workspace + * whose firebase.json already has a `firestore` section is left entirely untouched: its section + * may point at differently-named files, and creating the default-named ones would only add + * orphans nothing references. + * + * `firebaseJsonConfig`, when passed, should be the post-init snapshot of firebase.json (read + * after the last `firebaseTools.init` call and before `addFirestoreToFirebaseJson` runs) — see + * the call site in `ngAddSetupProject` for why that order matters. Passing a stale pre-init + * snapshot risks staging the default-named starter files on top of files firebase-tools already + * wrote to disk, colliding when the Tree commits. + */ +export const createFirestoreStarterFiles = ( + host: Tree, + context: SchematicContext, + features: FEATURES[], + firebaseJsonConfig?: FirebaseJSON, +) => { + if (!features.includes(FEATURES.Firestore)) { return; } + if (firebaseJsonConfig?.firestore) { + context.logger.info('firebase.json already has a firestore section — leaving its rules and indexes untouched.'); + return; + } + if (host.exists('/firestore.rules')) { + context.logger.info('firestore.rules already exists — leaving it untouched.'); + } else { + host.create('/firestore.rules', testModeFirestoreRules()); + context.logger.warn( + 'Generated firestore.rules in test mode: anyone can read and write your database until the rules expire in 30 days. ' + + 'Write real security rules before then — https://firebase.google.com/docs/rules' + ); + } + if (!host.exists('/firestore.indexes.json')) { + // Must exist once firebase.json references it — `firebase deploy` errors on a missing indexes file. + host.create('/firestore.indexes.json', stringifyFormatted({ indexes: [], fieldOverrides: [] })); + } +}; + +/** + * When Firestore is selected, points the `firestore` section of `firebase.json` at the starter + * files so `firebase deploy` includes rules and indexes. Written to the real filesystem (not the + * schematic Tree) because firebase-tools reads and rewrites `firebase.json` during the same + * `ng add` run; call this after the last `firebaseTools.init` call for that reason. + */ +export const addFirestoreToFirebaseJson = ( + projectRoot: string, + context: SchematicContext, + features: FEATURES[], +) => { + if (!features.includes(FEATURES.Firestore)) { return; } + // firebaseTools.init calls may have rewritten firebase.json on disk, so re-read it, add the + // firestore section if absent, and write it back. The whole read-check-write is guarded: a + // failed read (missing or unparseable file), a parse that isn't a usable object (reading + // `.firestore` off `null`/`undefined` throws; assigning it on a non-object primitive throws + // too), or a failed write (disk full, permissions) all warn and leave the file for the user to + // finish, rather than crashing the schematic with a raw Node error. + try { + const firebaseJson: FirebaseJSON = JSON.parse(readFileSync(join(projectRoot, 'firebase.json')).toString()); + if (!firebaseJson.firestore) { + firebaseJson.firestore = { rules: 'firestore.rules', indexes: 'firestore.indexes.json' }; + writeFileSync(join(projectRoot, 'firebase.json'), stringifyFormatted(firebaseJson)); + } + } catch (e) { + context.logger.warn( + `Could not update firebase.json with the firestore section (${e.message}). Check firebase.json ` + + 'and add { "firestore": { "rules": "firestore.rules", "indexes": "firestore.indexes.json" } } manually.' + ); + } +}; diff --git a/src/schematics/setup/index.ts b/src/schematics/setup/index.ts new file mode 100644 index 000000000..8a657e17a --- /dev/null +++ b/src/schematics/setup/index.ts @@ -0,0 +1,179 @@ +import { readFileSync, writeFileSync } from 'fs'; +import { join } from 'path'; +import { asWindowsPath, normalize } from '@angular-devkit/core'; +import { SchematicContext, Tree, chain } from '@angular-devkit/schematics'; +import { addRootProvider } from '@schematics/angular/utility'; +import { getFirebaseTools } from '../firebaseTools'; +import { + DataConnectConnectorConfig, + DeployOptions, FEATURES, FirebaseApp, FirebaseJSON, FirebaseProject, +} from '../interfaces'; +import { + addIgnoreFiles, + featureToRules, + getFirebaseProjectNameFromHost, + getProject, + parseDataConnectConfig, + setupTanstackDependencies, +} from '../utils'; +import { + addFirestoreToFirebaseJson, + createFirestoreStarterFiles, + setDefaultProjectInFirebaseRc, +} from './firebaseConfigs'; +import { appPrompt, featuresPrompt, featuresPromptMessage, projectPrompt, userPrompt } from './prompts'; + +// FirebaseOptions keys — apps.sdkconfig responses include management-API extras that initializeApp() rejects. +const firebaseOptionsKeys = [ + 'apiKey', 'authDomain', 'databaseURL', 'projectId', 'storageBucket', + 'messagingSenderId', 'appId', 'measurementId', 'recaptchaSiteKey', +]; + +export interface SetupConfig extends DeployOptions { + firebaseProject: FirebaseProject, + firebaseApp?: FirebaseApp, + sdkConfig?: Record, + firebaseJsonConfig?: FirebaseJSON; + dataConnectConfig?: DataConnectConnectorConfig | null; + firebaseJsonPath: string; +} + +export const setupProject = + (tree: Tree, context: SchematicContext, features: FEATURES[], config: SetupConfig) => { + const { projectName } = getProject(config, tree); + + addIgnoreFiles(tree); + + if (features.length) { + return chain([ + addRootProvider(projectName, ({code, external}) => { + external('initializeApp', '@angular/fire/app'); + return code`${external('provideFirebaseApp', '@angular/fire/app')}(() => initializeApp(${ + config.sdkConfig ? `{ ${Object.entries(config.sdkConfig).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(", ")} }` : "" + }))`; + }), + ...featureToRules(features, projectName, config.dataConnectConfig), + ]); + } +}; + +export const ngAddSetupProject = ( + options: DeployOptions +) => async (host: Tree, context: SchematicContext) => { + + // TODO is there a public API for this? + let projectRoot: string = (host as any)._backend._root; + if (process.platform.startsWith('win32')) { projectRoot = asWindowsPath(normalize(projectRoot)); } + + const features = await featuresPrompt(); + + if (features.length === 0) { + context.logger.warn( + 'No features were selected, so there is nothing to set up. ' + + `At the "${featuresPromptMessage}" prompt, use the arrow keys to move, ` + + 'press Space to select each feature you want, then Enter to confirm. ' + + 'Re-run ng add @angular/fire to try again.' + ); + } else { + + const firebaseTools = await getFirebaseTools(); + + // Add the firebase files if they don't exist already so login.use works + if (!host.exists('/firebase.json')) { writeFileSync(join(projectRoot, 'firebase.json'), '{}'); } + + let firebaseJson: FirebaseJSON = JSON.parse( + readFileSync(join(projectRoot, "firebase.json")).toString() + ); + + const user = await userPrompt({ projectRoot }); + const defaultUser = await firebaseTools.login(options); + if (user.email !== defaultUser?.email) { + await firebaseTools.login.use(user.email, { projectRoot }); + } + + const { projectName: ngProjectName } = getProject(options, host); + + const [ defaultProjectName ] = getFirebaseProjectNameFromHost(host, ngProjectName); + + const firebaseProject = await projectPrompt(defaultProjectName, { projectRoot, account: user.email }); + + let firebaseApp: FirebaseApp|undefined; + let sdkConfig: Record|undefined; + + const setupConfig: SetupConfig = { + ...options, firebaseProject, firebaseApp, sdkConfig, + firebaseJsonConfig: firebaseJson, + firebaseJsonPath: projectRoot + }; + if (features.length) { + + firebaseApp = await appPrompt(firebaseProject, undefined, { projectRoot }); + + const result = await firebaseTools.apps.sdkconfig('web', firebaseApp.appId, { nonInteractive: true, projectRoot }); + sdkConfig = Object.fromEntries( + firebaseOptionsKeys + .filter(key => result.sdkConfig[key] !== undefined) + .map(key => [key, result.sdkConfig[key]]) + ); + setupConfig.sdkConfig = sdkConfig; + setupConfig.firebaseApp = firebaseApp; + // set up data connect locally if data connect hasn't already been initialized. + if(features.includes(FEATURES.DataConnect)) { + if (!firebaseJson.dataconnect) { + try { + await firebaseTools.init("dataconnect", { + projectRoot, + project: firebaseProject.projectId, + }); + // Update firebaseJson values to include newly added dataconnect field in firebase.json. + firebaseJson = JSON.parse( + readFileSync(join(projectRoot, "firebase.json")).toString() + ); + setupConfig.firebaseJsonConfig = firebaseJson; + } catch (e) { + console.error(e); + } + } + let dataConnectConfig = parseDataConnectConfig(setupConfig); + if(!dataConnectConfig?.connectorYaml.generate?.javascriptSdk) { + await firebaseTools.init("dataconnect:sdk", { + projectRoot, + project: firebaseProject.projectId, + }); + } + // Parse through sdk again + dataConnectConfig = parseDataConnectConfig(setupConfig); + if(dataConnectConfig?.angular) { + context.logger.info('Generated Angular SDK Enabled.'); + } else { + context.logger.info('Generated Angular SDK Disabled. Please add `angular: true` to your connector.yaml'); + } + setupTanstackDependencies(host, context); + setupConfig.dataConnectConfig = dataConnectConfig; + } + + } + + // Read after the init calls, never before — firebase-tools rewrites firebase.json on disk + // mid-run. A failed read isn't fatal: createFirestoreStarterFiles falls back to checking the + // disk for each file it would create. + let firebaseJsonAfterInit: FirebaseJSON | undefined; + try { + firebaseJsonAfterInit = JSON.parse( + readFileSync(join(projectRoot, "firebase.json")).toString() + ); + } catch (e) { + context.logger.warn(`Could not re-read firebase.json after setup (${e.message}).`); + } + // Must run before addFirestoreToFirebaseJson: that call adds the firestore section, and if it + // ran first this snapshot would see it and skip creating the files it points at. + createFirestoreStarterFiles(host, context, features, firebaseJsonAfterInit); + + // Both write the real filesystem, after the last firebaseTools.init call — init writes + // .firebaserc and rewrites firebase.json on disk during the run. + setDefaultProjectInFirebaseRc(projectRoot, firebaseProject.projectId); + addFirestoreToFirebaseJson(projectRoot, context, features); + + return setupProject(host, context, features, setupConfig); + } +}; diff --git a/src/schematics/setup/prompts.ts b/src/schematics/setup/prompts.ts new file mode 100644 index 000000000..701713b96 --- /dev/null +++ b/src/schematics/setup/prompts.ts @@ -0,0 +1,177 @@ +import { spawnSync } from 'child_process'; +import * as fuzzy from 'fuzzy'; +import * as inquirer from 'inquirer'; +import { getFirebaseTools } from '../firebaseTools'; +import { FEATURES, FirebaseApp, FirebaseProject, featureOptions } from '../interfaces'; +import { shortAppId } from '../utils'; + + +inquirer.registerPrompt('autocomplete', require('inquirer-autocomplete-prompt')); + +const NEW_OPTION = '~~angularfire-new~~'; + +// `fuzzy` passes either the original list of projects or an internal object +// which contains the project as a property. +const isProject = (elem: FirebaseProject | fuzzy.FilterResult): elem is FirebaseProject => { + return (elem as { original: FirebaseProject }).original === undefined; +}; + +const isApp = (elem: FirebaseApp | fuzzy.FilterResult): elem is FirebaseApp => { + return (elem as { original: FirebaseApp }).original === undefined; +}; + +export const searchProjects = (projects: FirebaseProject[]) => + // eslint-disable-next-line @typescript-eslint/require-await + async (_: any, input: string) => { + projects.unshift({ + projectId: NEW_OPTION, + displayName: '[CREATE NEW PROJECT]' + } as any); + return fuzzy.filter(input, projects, { + extract(el) { + return `${el.projectId} ${el.displayName}`; + } + }).map((result) => { + let original: FirebaseProject; + if (isProject(result)) { + original = result; + } else { + original = result.original; + } + return { + name: original.displayName, + title: original.displayName, + value: original.projectId + }; + }); + }; + +export const searchApps = (apps: FirebaseApp[]) => + // eslint-disable-next-line @typescript-eslint/require-await + async (_: any, input: string) => { + apps.unshift({ + appId: NEW_OPTION, + displayName: '[CREATE NEW APP]', + } as any); + return fuzzy.filter(input, apps, { + extract(el: FirebaseApp) { + return el.displayName; + } + }).map((result) => { + let original: FirebaseApp; + if (isApp(result)) { + original = result; + } else { + original = result.original; + } + return { + name: original.displayName, + title: original.displayName, + value: shortAppId(original), + }; + }); + }; + +type Prompt = (questions: { name: K, source: (...args) => + Promise<{ value: U }[]>, default?: U | ((o: U[]) => U | Promise), [key: string]: any }) => + Promise>; + +const autocomplete: Prompt = (questions) => inquirer.prompt(questions); + + +export const featuresPromptMessage = 'What features would you like to setup?'; + +export const featuresPrompt = async (): Promise => { + const { features } = await inquirer.prompt({ + type: 'checkbox', + name: 'features', + choices: featureOptions, + message: featuresPromptMessage, + default: [], + }) as { features: FEATURES[] }; + return features; +}; + +export const userPrompt = async (options: { projectRoot: string }): Promise> => { + const firebaseTools = await getFirebaseTools(); + let loginList = await firebaseTools.login.list(); + if (!Array.isArray(loginList) || loginList.length === 0) { + spawnSync('firebase login', { shell: true, cwd: options.projectRoot, stdio: 'inherit' }); + return await firebaseTools.login(options); + } else { + const defaultUser = await firebaseTools.login(options); + const choices = loginList.map(({user}) => ({ name: user.email, value: user })); + const newChoice = { name: '[Login in with another account]', value: NEW_OPTION }; + const { user } = await inquirer.prompt({ + type: 'list', + name: 'user', + choices: [newChoice].concat(choices as any), // TODO types + message: 'Which Firebase account would you like to use?', + default: choices.find(it => it.value.email === defaultUser.email)?.value, + }) as any; + if (user === NEW_OPTION) { + spawnSync('firebase login:add', { shell: true, cwd: options.projectRoot, stdio: 'inherit' }); + loginList = await firebaseTools.login.list(); + if (!Array.isArray(loginList)) { + throw new Error("firebase login:list did not respond as expected"); + } + const priorEmails = choices.map(it => it.name); + const newLogin = loginList.find(it => !priorEmails.includes(it.user.email)); + if (!newLogin) { + throw new Error("Did not find a new user."); + } + return newLogin.user; + } + return user; + } +}; + +export const projectPrompt = async (defaultProject: string|undefined, options: unknown) => { + const firebaseTools = await getFirebaseTools(); + const projects = await firebaseTools.projects.list(options); + const { projectId } = await autocomplete({ + type: 'autocomplete', + name: 'projectId', + source: searchProjects(projects), + message: 'Please select a project:', + default: defaultProject, + }); + if (projectId === NEW_OPTION) { + const { projectId } = await inquirer.prompt({ + type: 'input', + name: 'projectId', + message: `Please specify a unique project id (cannot be modified afterward) [6-30 characters]:`, + }) as { projectId: string }; + const { displayName } = await inquirer.prompt({ + type: 'input', + name: 'displayName', + message: 'What would you like to call your project?', + default: projectId, + }) as { displayName: string }; + return await firebaseTools.projects.create(projectId, { account: (options as any).account, displayName, nonInteractive: true }); + } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return (projects).find(it => it.projectId === projectId)!; +}; + +export const appPrompt = async ({ projectId: project }: FirebaseProject, defaultAppId: string|undefined, options: any) => { + const firebaseTools = await getFirebaseTools(); + const apps = await firebaseTools.apps.list('web', { ...options, project }); + const { appId } = await autocomplete({ + type: 'autocomplete', + name: 'appId', + source: searchApps(apps), + message: 'Please select an app:', + default: defaultAppId, + }); + if (appId === NEW_OPTION) { + const { displayName } = await inquirer.prompt({ + type: 'input', + name: 'displayName', + message: 'What would you like to call your app?', + }) as { displayName: string }; + return await firebaseTools.apps.create('web', displayName, { ...options, nonInteractive: true, project }); + } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return (apps).find(it => shortAppId(it) === appId)!; +}; diff --git a/src/schematics/setup/schema.json b/src/schematics/setup/schema.json new file mode 100644 index 000000000..5b1ce2c4f --- /dev/null +++ b/src/schematics/setup/schema.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "angular-fire-ng-add", + "title": "AngularFire ng-add", + "type": "object", + "properties": { + "project": { + "type": "string", + "description": "The name of the project.", + "$default": { + "$source": "projectName" + } + } + }, + "required": [] + } diff --git a/src/schematics/tsconfig.json b/src/schematics/tsconfig.json index 10bc3028e..bc0b86afd 100644 --- a/src/schematics/tsconfig.json +++ b/src/schematics/tsconfig.json @@ -7,6 +7,7 @@ "removeComments": true, "strictNullChecks": true, "resolveJsonModule": true, + "esModuleInterop": true, "lib": [ "es2015", "dom", @@ -16,9 +17,17 @@ ], "skipLibCheck": true, "moduleResolution": "node", - "target": "es6", + "target": "es2018", "module": "commonjs", "outDir": "../../dist/packages-dist/schematics" }, - "files": ["public_api.ts"] + "files": [ + "update/index.ts", + "deploy/actions.ts", + "deploy/builder.ts", + "add/index.ts", + "setup/index.ts", + "update/v7/index.ts", + "update/v21/index.ts", + ] } \ No newline at end of file diff --git a/src/schematics/update/index.ts b/src/schematics/update/index.ts new file mode 100644 index 000000000..7b57b5ab4 --- /dev/null +++ b/src/schematics/update/index.ts @@ -0,0 +1,8 @@ +import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics'; + +export const ngPostUpdate = (): Rule => ( + host: Tree, + _context: SchematicContext +) => { + return host; +}; diff --git a/src/schematics/update/v21/index.jasmine.ts b/src/schematics/update/v21/index.jasmine.ts new file mode 100644 index 000000000..2911d060b --- /dev/null +++ b/src/schematics/update/v21/index.jasmine.ts @@ -0,0 +1,85 @@ +import { logging } from '@angular-devkit/core'; +import { HostTree, SchematicContext } from '@angular-devkit/schematics'; +import * as typescript from 'typescript'; +import { firebaseVersionRange } from '../../common.js'; +import { ngUpdate } from './index.js'; +import 'jasmine'; + +const contextWithTaskSpy = () => { + const addTask = jasmine.createSpy('addTask'); + const context = { + logger: new logging.Logger('test'), + addTask, + } as unknown as SchematicContext; + return { context, addTask }; +}; + +const treeWithFirebase = (firebaseVersion?: string) => { + const tree = new HostTree(); + tree.create('package.json', JSON.stringify({ + name: 'test-app', + dependencies: firebaseVersion === undefined ? {} : { firebase: firebaseVersion }, + }, null, 2)); + return tree; +}; + +describe('migration-v21 ngUpdate', () => { + + it('aligns a stale firebase range and schedules an install', () => { + const { context, addTask } = contextWithTaskSpy(); + const tree = treeWithFirebase('^11.0.0'); + ngUpdate()(tree, context); + const written = JSON.parse(tree.readText('package.json')); + expect(written.dependencies.firebase).toBe(firebaseVersionRange); + expect(addTask).toHaveBeenCalledTimes(1); + }); + + it('schedules nothing when the workspace is already aligned', () => { + const { context, addTask } = contextWithTaskSpy(); + const tree = treeWithFirebase(firebaseVersionRange); + ngUpdate()(tree, context); + expect(addTask).not.toHaveBeenCalled(); + }); + + it('keeps the firebase alignment when the rewrite throws', () => { + const logger = new logging.Logger('test'); + const warn = spyOn(logger, 'warn'); + const addTask = jasmine.createSpy('addTask'); + const context = { logger, addTask } as unknown as SchematicContext; + const tree = treeWithFirebase('^11.0.0'); + tree.create('angular.json', JSON.stringify({ + projects: { app: { root: '', sourceRoot: 'src' } }, + })); + tree.create('src/app/foo.ts', `import { getVertexAI } from '@angular/fire/vertexai';`); + const throwingCompiler = { + ScriptTarget: typescript.ScriptTarget, + createSourceFile: () => { throw new Error('boom'); }, + } as unknown as typeof typescript; + + ngUpdate({ compiler: throwingCompiler })(tree, context); + + // The rewrite failure costs only the rewrite, never the firebase alignment. + const written = JSON.parse(tree.readText('package.json')); + expect(written.dependencies.firebase).toBe(firebaseVersionRange); + expect(addTask).toHaveBeenCalledTimes(1); + expect(warn.calls.allArgs().map(callArgs => String(callArgs[0])).join('\n')) + .toContain('Skipped the Vertex AI -> AI Logic source rewrite'); + }); + + it('runs the Vertex AI rewrite and the alignment through one ngUpdate call', () => { + const { context, addTask } = contextWithTaskSpy(); + const tree = treeWithFirebase('^11.0.0'); + tree.create('angular.json', JSON.stringify({ + projects: { app: { root: '', sourceRoot: 'src' } }, + })); + tree.create('src/app/foo.ts', `import { getVertexAI } from '@angular/fire/vertexai';`); + + ngUpdate({ compiler: typescript })(tree, context); + + expect(tree.readText('src/app/foo.ts')).toBe(`import { getAI } from '@angular/fire/ai';`); + const written = JSON.parse(tree.readText('package.json')); + expect(written.dependencies.firebase).toBe(firebaseVersionRange); + expect(addTask).toHaveBeenCalledTimes(1); + }); + +}); diff --git a/src/schematics/update/v21/index.ts b/src/schematics/update/v21/index.ts new file mode 100644 index 000000000..b63788985 --- /dev/null +++ b/src/schematics/update/v21/index.ts @@ -0,0 +1,31 @@ +import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics'; +// The explicit index.js subpath keeps this importable from the ESM jasmine run. The bare +// /tasks directory specifier only resolves under CommonJS. +import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks/index.js'; +import type * as ts from 'typescript'; +import { alignFirebaseVersion } from '../../common.js'; +import { rewriteVertexAIToAI } from './vertexai-to-ai/index.js'; + +// ng update re-runs this migration on rc-to-stable transitions (the CLI clamps the migration +// range's upper bound to the release version), so it must stay a no-op when nothing changes. +export const ngUpdate = (options?: { compiler?: typeof ts }): Rule => ( + host: Tree, + context: SchematicContext +) => { + // Align firebase before anything else: it is the one step users cannot do without, so no + // failure below may cost it. This step changes dependencies, so only it schedules an install. + if (alignFirebaseVersion(host, context)) { + context.addTask(new NodePackageInstallTask()); + } + // Rewrite Vertex AI imports to AI Logic (source-only edits, no dependency change). Guarded so + // an unexpected rewrite failure costs only the rewrite, never the alignment above. + try { + rewriteVertexAIToAI(host, context, options?.compiler); + } catch (error) { + context.logger.warn( + `Skipped the Vertex AI -> AI Logic source rewrite: ${error}. ` + + 'Any remaining @angular/fire/vertexai imports need a manual migration - see the v21 upgrade guide (docs/version-21-upgrade.md).' + ); + } + return host; +}; diff --git a/src/schematics/update/v21/vertexai-to-ai.jasmine.ts b/src/schematics/update/v21/vertexai-to-ai.jasmine.ts new file mode 100644 index 000000000..ca89596f1 --- /dev/null +++ b/src/schematics/update/v21/vertexai-to-ai.jasmine.ts @@ -0,0 +1,958 @@ +import { logging } from '@angular-devkit/core'; +import { HostTree, SchematicContext } from '@angular-devkit/schematics'; +import * as typescript from 'typescript'; +import { applyEdits, rewriteVertexAIToAI } from './vertexai-to-ai/index.js'; +import 'jasmine'; + +const context = { logger: new logging.Logger('test') } as unknown as SchematicContext; + +const contextWithLogSpies = () => { + const logger = new logging.Logger('test'); + const warn = spyOn(logger, 'warn'); + const info = spyOn(logger, 'info'); + const spiedContext = { logger } as unknown as SchematicContext; + return { context: spiedContext, warn, info }; +}; + +const treeWith = (files: Record) => { + const tree = new HostTree(); + tree.create('angular.json', JSON.stringify({ + projects: { app: { root: '', sourceRoot: 'src' } }, + })); + Object.entries(files).forEach(([path, content]) => tree.create(path, content)); + return tree; +}; + +describe('rewriteVertexAIToAI', () => { + + it('rewrites a named import and its usages, keeping getVertexAI calls on the Vertex AI backend', () => { + const source = [ + `import { provideVertexAI, getVertexAI, VertexAI } from '@angular/fire/vertexai';`, + `import { inject } from '@angular/core';`, + ``, + `export const providers = [provideVertexAI(() => getVertexAI())];`, + `export class Foo { private ai = inject(VertexAI); }`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + const changed = rewriteVertexAIToAI(tree, context, typescript); + + expect(changed).toBe(true); + const out = tree.readText('src/app/foo.ts'); + expect(out).toContain(`import { provideAI, getAI, VertexAIBackend, AI } from '@angular/fire/ai';`); + expect(out).toContain('provideAI(() => getAI(undefined, { backend: new VertexAIBackend() }))'); + expect(out).toContain('inject(AI)'); + expect(out).not.toContain('getVertexAI'); + expect(out).not.toContain('provideVertexAI'); + expect(out).not.toContain('@angular/fire/vertexai'); + }); + + it('passes a lone app argument through to the rewritten call', () => { + const source = [ + `import { getVertexAI } from '@angular/fire/vertexai';`, + `import { getApp } from '@angular/fire/app';`, + `export const vertex = getVertexAI(getApp());`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + + const out = tree.readText('src/app/foo.ts'); + expect(out).toContain(`import { getAI, VertexAIBackend } from '@angular/fire/ai';`); + expect(out).toContain('getAI(getApp(), { backend: new VertexAIBackend() })'); + }); + + it('moves a literal location option into the VertexAIBackend constructor', () => { + const source = [ + `import { getVertexAI } from '@angular/fire/vertexai';`, + `import { getApp } from '@angular/fire/app';`, + `export const vertex = getVertexAI(getApp(), { location: 'europe-west1' });`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + + expect(tree.readText('src/app/foo.ts')) + .toContain(`getAI(getApp(), { backend: new VertexAIBackend('europe-west1') })`); + }); + + it('replaces an empty options literal with the backend object', () => { + const source = [ + `import { getVertexAI } from '@angular/fire/vertexai';`, + `import { getApp } from '@angular/fire/app';`, + `export const vertex = getVertexAI(getApp(), {});`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + + expect(tree.readText('src/app/foo.ts')) + .toContain('getAI(getApp(), { backend: new VertexAIBackend() })'); + }); + + it('logs each rewritten getVertexAI call', () => { + const { context: spiedContext, info } = contextWithLogSpies(); + const source = [ + `import { getVertexAI } from '@angular/fire/vertexai';`, + `export const vertex = getVertexAI();`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, spiedContext, typescript); + + expect(info).toHaveBeenCalledTimes(1); + expect(info.calls.mostRecent().args[0]).toContain('/src/app/foo.ts:2'); + expect(info.calls.mostRecent().args[0]).toContain('Vertex AI backend'); + }); + + it('leaves a getVertexAI call with non-literal options in place and warns', () => { + const { context: spiedContext, warn } = contextWithLogSpies(); + const source = [ + `import { provideVertexAI, getVertexAI } from '@angular/fire/vertexai';`, + `declare const options: { location: string };`, + `export const vertex = getVertexAI(undefined, options);`, + `export const p = provideVertexAI(() => vertex);`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + const changed = rewriteVertexAIToAI(tree, spiedContext, typescript); + + expect(changed).toBe(true); + const out = tree.readText('src/app/foo.ts'); + // Other symbols and the module specifier still migrate. getVertexAI is left whole so the + // stale import fails to compile loudly instead of silently changing backends. + expect(out).toContain(`import { provideAI, getVertexAI } from '@angular/fire/ai';`); + expect(out).toContain('getVertexAI(undefined, options)'); + expect(warn).toHaveBeenCalledTimes(1); + expect(warn.calls.mostRecent().args[0]).toContain('Gemini Developer API'); + }); + + it('leaves every getVertexAI edit out of a file where the binding is used as a value', () => { + const { context: spiedContext, warn } = contextWithLogSpies(); + const source = [ + `import { getVertexAI } from '@angular/fire/vertexai';`, + `export const factory = getVertexAI;`, + `export const vertex = getVertexAI();`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, spiedContext, typescript); + + const out = tree.readText('src/app/foo.ts'); + expect(out).toContain(`import { getVertexAI } from '@angular/fire/ai';`); + expect(out).toContain('const factory = getVertexAI;'); + // The direct call would be rewritable alone, but a half-renamed binding cannot compile. + expect(out).toContain('const vertex = getVertexAI();'); + // One warning for the value use, one for the skipped-but-rewritable call. + expect(warn).toHaveBeenCalledTimes(2); + const warnText = warn.calls.allArgs().map(callArgs => String(callArgs[0])).join('\n'); + expect(warnText).toContain('left a rewritable getVertexAI call'); + expect(warnText).toContain('kept together'); + }); + + it('repurposes the getVertexAI specifier when getAI is already imported', () => { + const source = [ + `import { getAI, getVertexAI } from '@angular/fire/vertexai';`, + `export const genAI = getAI();`, + `export const vertex = getVertexAI();`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + + const out = tree.readText('src/app/foo.ts'); + expect(out).toContain(`import { getAI, VertexAIBackend } from '@angular/fire/ai';`); + // The plain getAI call is untouched, and only the getVertexAI call gets the backend pin. + expect(out).toContain('const genAI = getAI();'); + expect(out).toContain('const vertex = getAI(undefined, { backend: new VertexAIBackend() });'); + }); + + it('drops an unused getVertexAI specifier when getAI is already imported', () => { + const tree = treeWith({ + 'src/app/foo.ts': `import { getAI, getVertexAI } from '@angular/fire/vertexai';`, + }); + + rewriteVertexAIToAI(tree, context, typescript); + + expect(tree.readText('src/app/foo.ts')).toBe(`import { getAI } from '@angular/fire/ai';`); + }); + + it('keeps the local name of an aliased getVertexAI import while pinning its calls', () => { + const source = [ + `import { getVertexAI as gv } from '@angular/fire/vertexai';`, + `import { getApp } from '@angular/fire/app';`, + `export const vertex = gv(getApp());`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + + const out = tree.readText('src/app/foo.ts'); + expect(out).toContain(`import { getAI as gv, VertexAIBackend } from '@angular/fire/ai';`); + expect(out).toContain('gv(getApp(), { backend: new VertexAIBackend() })'); + }); + + it('leaves a getVertexAI re-export in place and warns', () => { + const { context: spiedContext, warn } = contextWithLogSpies(); + const tree = treeWith({ + 'src/app/foo.ts': `export { getVertexAI } from '@angular/fire/vertexai';`, + }); + + const changed = rewriteVertexAIToAI(tree, spiedContext, typescript); + + expect(changed).toBe(true); + expect(tree.readText('src/app/foo.ts')).toBe(`export { getVertexAI } from '@angular/fire/ai';`); + expect(warn).toHaveBeenCalledTimes(1); + expect(warn.calls.mostRecent().args[0]).toContain('re-export of getVertexAI'); + }); + + it('preserves the public export name of an un-aliased re-export with a from clause', () => { + const tree = treeWith({ + 'src/app/foo.ts': `export { VertexAI, VertexAIModule as Legacy } from '@angular/fire/vertexai';`, + }); + + rewriteVertexAIToAI(tree, context, typescript); + + expect(tree.readText('src/app/foo.ts')) + .toBe(`export { AI as VertexAI, AIModule as Legacy } from '@angular/fire/ai';`); + }); + + it('rewrites direct firebase SDK imports the same way', () => { + const source = [ + `import { getVertexAI, VertexAIError } from 'firebase/vertexai';`, + `import { getApp } from 'firebase/app';`, + `export const vertex = getVertexAI(getApp());`, + `export const isAIError = (e: unknown) => e instanceof VertexAIError;`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + + const out = tree.readText('src/app/foo.ts'); + expect(out).toContain(`import { getAI, VertexAIBackend, AIError } from 'firebase/ai';`); + expect(out).toContain('getAI(getApp(), { backend: new VertexAIBackend() })'); + expect(out).toContain('instanceof AIError'); + }); + + it('renames only the imported name for an aliased import, leaving usages of the alias', () => { + const source = [ + `import { VertexAI as MyAI } from '@angular/fire/vertexai';`, + `import { inject } from '@angular/core';`, + `export class Foo { private ai = inject(MyAI); }`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + + const out = tree.readText('src/app/foo.ts'); + expect(out).toContain(`import { AI as MyAI } from '@angular/fire/ai';`); + expect(out).toContain('inject(MyAI)'); + }); + + it('handles the older vertexai-preview entry point', () => { + const tree = treeWith({ + 'src/app/foo.ts': `import { getVertexAI } from '@angular/fire/vertexai-preview';`, + }); + + rewriteVertexAIToAI(tree, context, typescript); + + expect(tree.readText('src/app/foo.ts')).toBe(`import { getAI } from '@angular/fire/ai';`); + }); + + it('rewrites namespace-import member accesses, reaching the backend class through the namespace', () => { + const source = [ + `import * as vai from '@angular/fire/vertexai';`, + `export const p = vai.provideVertexAI(() => vai.getVertexAI());`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + + const out = tree.readText('src/app/foo.ts'); + expect(out).toContain(`import * as vai from '@angular/fire/ai';`); + expect(out).toContain('vai.provideAI(() => vai.getAI(undefined, { backend: new vai.VertexAIBackend() }))'); + }); + + it('leaves unchanged symbols alone', () => { + const tree = treeWith({ + 'src/app/foo.ts': `import { getGenerativeModel, getVertexAI } from '@angular/fire/vertexai';`, + }); + + rewriteVertexAIToAI(tree, context, typescript); + + expect(tree.readText('src/app/foo.ts')) + .toBe(`import { getGenerativeModel, getAI } from '@angular/fire/ai';`); + }); + + it('does not touch strings, comments, or unrelated member names (the AST win over regex)', () => { + const source = [ + `import { VertexAI } from '@angular/fire/vertexai';`, + `import { inject } from '@angular/core';`, + `// VertexAI is now AI Logic`, + `export const label = 'VertexAI docs';`, + `export class Foo { VertexAI = 1; private ai = inject(VertexAI); }`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + + const out = tree.readText('src/app/foo.ts'); + // comment and string keep the old word + expect(out).toContain('// VertexAI is now AI Logic'); + expect(out).toContain(`'VertexAI docs'`); + // a class member literally named VertexAI is not the import binding, so it is untouched + expect(out).toContain('VertexAI = 1;'); + // the real usage and the import are rewritten + expect(out).toContain(`from '@angular/fire/ai'`); + expect(out).toContain('inject(AI)'); + }); + + it('is a no-op when there is nothing to rewrite', () => { + const tree = treeWith({ + 'src/app/foo.ts': `import { getAI } from '@angular/fire/ai';`, + }); + + const changed = rewriteVertexAIToAI(tree, context, typescript); + + expect(changed).toBe(false); + expect(tree.readText('src/app/foo.ts')).toBe(`import { getAI } from '@angular/fire/ai';`); + }); + + it('does not crash without an angular.json', () => { + const tree = new HostTree(); + tree.create('src/app/foo.ts', `import { getVertexAI } from '@angular/fire/vertexai';`); + + expect(() => rewriteVertexAIToAI(tree, context, typescript)).not.toThrow(); + }); + + it('rewrites files in a non-root project (library / multi-project workspace)', () => { + const tree = new HostTree(); + tree.create('angular.json', JSON.stringify({ + projects: { + app: { root: '', sourceRoot: 'src' }, + lib: { root: 'projects/lib', sourceRoot: 'projects/lib/src' }, + }, + })); + tree.create('projects/lib/src/foo.ts', `import { getVertexAI } from '@angular/fire/vertexai';`); + + const changed = rewriteVertexAIToAI(tree, context, typescript); + + expect(changed).toBe(true); + expect(tree.readText('projects/lib/src/foo.ts')).toBe(`import { getAI } from '@angular/fire/ai';`); + }); + + it('covers a root project with no sourceRoot (older CLI workspaces) without walking node_modules', () => { + const tree = new HostTree(); + tree.create('angular.json', JSON.stringify({ + projects: { app: { root: '' } }, + })); + tree.create('source/foo.ts', `import { getVertexAI } from '@angular/fire/vertexai';`); + tree.create('node_modules/some-dep/index.ts', `import { getVertexAI } from '@angular/fire/vertexai';`); + + const changed = rewriteVertexAIToAI(tree, context, typescript); + + expect(changed).toBe(true); + expect(tree.readText('source/foo.ts')).toBe(`import { getAI } from '@angular/fire/ai';`); + expect(tree.readText('node_modules/some-dep/index.ts')) + .toBe(`import { getVertexAI } from '@angular/fire/vertexai';`); + }); + + it('renames a renamed symbol used in type position', () => { + const source = [ + `import { VertexAI } from '@angular/fire/vertexai';`, + `export let x: VertexAI;`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + + expect(tree.readText('src/app/foo.ts')).toContain('let x: AI;'); + }); + + it('rewrites namespace member access in type position', () => { + const source = [ + `import * as fire from '@angular/fire/vertexai';`, + `export function f(): fire.VertexAI { return fire.getVertexAI(); }`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + + const out = tree.readText('src/app/foo.ts'); + expect(out).toContain('fire.AI'); + expect(out).toContain('fire.getAI(undefined, { backend: new fire.VertexAIBackend() })'); + expect(out).not.toContain('getVertexAI'); + }); + + it('leaves a shorthand property in place (it hands the function itself around) and warns', () => { + const { context: spiedContext, warn } = contextWithLogSpies(); + const source = [ + `import { getVertexAI } from '@angular/fire/vertexai';`, + `export const registry = { getVertexAI };`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, spiedContext, typescript); + + const out = tree.readText('src/app/foo.ts'); + expect(out).toContain(`import { getVertexAI } from '@angular/fire/ai';`); + // Anchored on the object literal: the import line also contains `{ getVertexAI }`, so a bare + // toContain would pass even if the shorthand were wrongly expanded. + expect(out).toContain('registry = { getVertexAI };'); + expect(out).not.toContain('getVertexAI: getAI'); + expect(warn).toHaveBeenCalled(); + }); + + it('preserves the export name for a bare local re-export', () => { + const source = [ + `import { VertexAI } from '@angular/fire/vertexai';`, + `export { VertexAI };`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + + const out = tree.readText('src/app/foo.ts'); + expect(out).toContain(`import { AI } from '@angular/fire/ai';`); + expect(out).toContain('export { AI as VertexAI };'); + }); + + it('renames the instances token, the instance observable, the model base class, and the error code', () => { + const tree = treeWith({ + 'src/app/foo.ts': `import { VertexAIInstances, vertexAIInstance$, VertexAIModel, VertexAIErrorCode } from '@angular/fire/vertexai';`, + }); + + rewriteVertexAIToAI(tree, context, typescript); + + expect(tree.readText('src/app/foo.ts')) + .toBe(`import { AIInstances, AIInstance$, AIModel, AIErrorCode } from '@angular/fire/ai';`); + }); + + it('does not rename a get/set accessor named like an imported symbol', () => { + const source = [ + `import { getVertexAI } from '@angular/fire/vertexai';`, + `export class Foo { get getVertexAI() { return 1; } }`, + `export const used = getVertexAI();`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + + const out = tree.readText('src/app/foo.ts'); + expect(out).toContain('get getVertexAI()'); + expect(out).toContain('getAI(undefined, { backend: new VertexAIBackend() })'); + }); + + it('does not treat a destructuring property key as a usage', () => { + const source = [ + `import { getVertexAI } from '@angular/fire/vertexai';`, + `export function a(obj: any) { const { getVertexAI: local } = obj; return local; }`, + `export const used = getVertexAI();`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + + const out = tree.readText('src/app/foo.ts'); + // the property key read from obj is not the import, so it is left untouched + expect(out).toContain('const { getVertexAI: local } = obj;'); + // the direct call is still rewritten + expect(out).toContain('getAI(undefined, { backend: new VertexAIBackend() })'); + }); + + it('leaves a binding initializer that hands the function around, and warns', () => { + const { context: spiedContext, warn } = contextWithLogSpies(); + const source = [ + `import { getVertexAI } from '@angular/fire/vertexai';`, + `export function b({ cb = getVertexAI }: any) { return cb; }`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, spiedContext, typescript); + + const out = tree.readText('src/app/foo.ts'); + expect(out).toContain(`import { getVertexAI } from '@angular/fire/ai';`); + expect(out).toContain('cb = getVertexAI'); + expect(warn).toHaveBeenCalled(); + }); + + it('leaves a file alone when a local declaration shadows getVertexAI, and warns', () => { + const { context: spiedContext, warn } = contextWithLogSpies(); + const source = [ + `import { getVertexAI } from '@angular/fire/vertexai';`, + `export function f(obj: any) { const { getVertexAI } = obj; return getVertexAI(); }`, + `export const vertex = getVertexAI();`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, spiedContext, typescript); + + const out = tree.readText('src/app/foo.ts'); + // Name-based matching cannot tell the destructured local from the import, so nothing but the + // module specifier changes and the import breaks loudly instead of redirecting the local call. + expect(out).toContain(`import { getVertexAI } from '@angular/fire/ai';`); + expect(out).toContain('const { getVertexAI } = obj; return getVertexAI();'); + expect(out).toContain('export const vertex = getVertexAI();'); + expect(warn).toHaveBeenCalledTimes(1); + expect(warn.calls.mostRecent().args[0]).toContain('declares a local named `getVertexAI`'); + expect(warn.calls.mostRecent().args[0]).toContain('import path itself still moves'); + }); + + it('skips a renamed symbol whose name is shadowed by a local, and warns', () => { + const { context: spiedContext, warn } = contextWithLogSpies(); + const source = [ + `import { provideVertexAI, VertexAI } from '@angular/fire/vertexai';`, + `import { inject } from '@angular/core';`, + `export function f(obj: any) { const { provideVertexAI } = obj; return provideVertexAI(); }`, + `export class Foo { private ai = inject(VertexAI); }`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, spiedContext, typescript); + + const out = tree.readText('src/app/foo.ts'); + // provideVertexAI is excluded whole (its local shadow makes it ambiguous). VertexAI still migrates. + expect(out).toContain(`import { provideVertexAI, AI } from '@angular/fire/ai';`); + expect(out).toContain('return provideVertexAI();'); + expect(out).toContain('inject(AI)'); + expect(warn).toHaveBeenCalledTimes(1); + }); + + it('skips namespace member rewrites when the namespace name is shadowed, and warns', () => { + const { context: spiedContext, warn } = contextWithLogSpies(); + const source = [ + `import * as vai from '@angular/fire/vertexai';`, + `export function f(vai: any) { return vai.getVertexAI(); }`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, spiedContext, typescript); + + const out = tree.readText('src/app/foo.ts'); + expect(out).toContain(`import * as vai from '@angular/fire/ai';`); + expect(out).toContain('return vai.getVertexAI();'); + expect(warn).toHaveBeenCalledTimes(1); + }); + + it('adds VertexAIBackend once for two getVertexAI specifiers in one import', () => { + const source = [ + `import { getVertexAI, getVertexAI as gv2 } from '@angular/fire/vertexai';`, + `export const a = getVertexAI();`, + `export const b = gv2();`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + + const out = tree.readText('src/app/foo.ts'); + const importLine = out.split('\n')[0]; + expect(importLine).toBe(`import { getAI, VertexAIBackend, getAI as gv2 } from '@angular/fire/ai';`); + expect(out).toContain('const a = getAI(undefined, { backend: new VertexAIBackend() });'); + expect(out).toContain('const b = gv2(undefined, { backend: new VertexAIBackend() });'); + }); + + it('adds VertexAIBackend once when two old entry points are imported', () => { + const source = [ + `import { getVertexAI } from '@angular/fire/vertexai';`, + `import { getVertexAI as fbGet } from 'firebase/vertexai';`, + `export const a = getVertexAI();`, + `export const b = fbGet();`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + + const out = tree.readText('src/app/foo.ts'); + expect(out).toContain(`import { getAI, VertexAIBackend } from '@angular/fire/ai';`); + expect(out).toContain(`import { getAI as fbGet } from 'firebase/ai';`); + // One import plus two constructor calls: the backend class is never double-imported. + expect((out.match(/VertexAIBackend/g) || []).length).toBe(3); + }); + + it('repurposes the specifier when getAI is imported from the new entry point already', () => { + const source = [ + `import { getAI } from '@angular/fire/ai';`, + `import { getVertexAI } from '@angular/fire/vertexai';`, + `export const a = getAI();`, + `export const vertex = getVertexAI();`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + + const out = tree.readText('src/app/foo.ts'); + expect(out).toContain(`import { VertexAIBackend } from '@angular/fire/ai';`); + expect(out).toContain('const a = getAI();'); + expect(out).toContain('const vertex = getAI(undefined, { backend: new VertexAIBackend() });'); + }); + + it('leaves getVertexAI unmigrated when getAI is imported from an unrelated module, and warns', () => { + const { context: spiedContext, warn } = contextWithLogSpies(); + const source = [ + `import { getAI } from 'some-other-lib';`, + `import { getVertexAI } from '@angular/fire/vertexai';`, + `export const v = getVertexAI();`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, spiedContext, typescript); + + const out = tree.readText('src/app/foo.ts'); + // Rewriting the call to getAI would silently bind it to some-other-lib's getAI. + expect(out).toContain(`import { getAI } from 'some-other-lib';`); + expect(out).toContain(`import { getVertexAI } from '@angular/fire/ai';`); + expect(out).toContain('const v = getVertexAI();'); + const warnText = warn.calls.allArgs().map(callArgs => String(callArgs[0])).join('\n'); + expect(warnText).toContain('already bound here from a source other than AI Logic'); + expect(warnText).toContain('fails to compile there'); + // The skipped call's own warning names the real cause, the foreign binding. + expect(warnText).toContain('binds getAI or VertexAIBackend from another source'); + }); + + it('leaves getVertexAI unmigrated when the file declares its own getAI, and warns', () => { + const { context: spiedContext, warn } = contextWithLogSpies(); + const source = [ + `import { getVertexAI } from '@angular/fire/vertexai';`, + `export function getAI(value: unknown) { return value; }`, + `export const v = getVertexAI();`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, spiedContext, typescript); + + const out = tree.readText('src/app/foo.ts'); + // Injecting getAI would collide with the local declaration. + expect(out).toContain(`import { getVertexAI } from '@angular/fire/ai';`); + expect(out).toContain('const v = getVertexAI();'); + expect(warn).toHaveBeenCalled(); + }); + + it('leaves a star re-export of the old module unmigrated, and warns', () => { + const { context: spiedContext, warn } = contextWithLogSpies(); + const tree = treeWith({ + 'src/app/foo.ts': `export * from '@angular/fire/vertexai';`, + }); + + const changed = rewriteVertexAIToAI(tree, spiedContext, typescript); + + // Rewriting the specifier would silently rename every symbol this file re-exports. + expect(changed).toBe(false); + expect(tree.readText('src/app/foo.ts')).toBe(`export * from '@angular/fire/vertexai';`); + expect(warn).toHaveBeenCalledTimes(1); + expect(warn.calls.mostRecent().args[0]).toContain('re-exported public symbols'); + }); + + it('expands a renamed symbol used as an object shorthand value, keeping the property name', () => { + const source = [ + `import { provideVertexAI } from '@angular/fire/vertexai';`, + `export const reg = { provideVertexAI };`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + + expect(tree.readText('src/app/foo.ts')).toContain('reg = { provideVertexAI: provideAI };'); + }); + + it('moves a shorthand location option into the VertexAIBackend constructor', () => { + const source = [ + `import { getVertexAI } from '@angular/fire/vertexai';`, + `import { getApp } from '@angular/fire/app';`, + `const location = 'europe-west1';`, + `export const vertex = getVertexAI(getApp(), { location });`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + + expect(tree.readText('src/app/foo.ts')) + .toContain('getAI(getApp(), { backend: new VertexAIBackend(location) })'); + }); + + it('leaves a call whose location option references a rewritten symbol, and warns', () => { + const { context: spiedContext, warn } = contextWithLogSpies(); + const source = [ + `import { getVertexAI, VertexAI } from '@angular/fire/vertexai';`, + `declare const region: (marker: unknown) => string;`, + `export const a = getVertexAI(undefined, { location: region(VertexAI) });`, + `export const tail = 42;`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, spiedContext, typescript); + + const out = tree.readText('src/app/foo.ts'); + // Replacing the options span would overlap the rename inside it, so the call is left whole + // (with the inner rename still applied) and nothing after the call gets corrupted. + expect(out).toContain('getVertexAI(undefined, { location: region(AI) })'); + expect(out).toContain('export const tail = 42;'); + expect(warn.calls.mostRecent().args[0]).toContain('options mention other symbols this migration rewrites'); + }); + + it('leaves a call whose options nest another getVertexAI call, and warns', () => { + const { context: spiedContext, warn } = contextWithLogSpies(); + const source = [ + `import { getVertexAI } from '@angular/fire/vertexai';`, + `declare const pick: (value: unknown) => string;`, + `export const a = getVertexAI(undefined, { location: pick(getVertexAI()) });`, + `export const tail = 42;`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, spiedContext, typescript); + + const out = tree.readText('src/app/foo.ts'); + expect(out).toContain('getVertexAI(undefined, { location: pick(getVertexAI()) })'); + expect(out).toContain('export const tail = 42;'); + // One warning for the unsupported outer call, one for the blocked inner call. + expect(warn).toHaveBeenCalledTimes(2); + }); + + it('leaves a namespace call whose options reference the namespace, and warns', () => { + const { context: spiedContext, warn } = contextWithLogSpies(); + const source = [ + `import * as vai from '@angular/fire/vertexai';`, + `export const a = vai.getVertexAI(undefined, { location: vai.VertexAIModel });`, + `export const tail = 42;`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, spiedContext, typescript); + + const out = tree.readText('src/app/foo.ts'); + expect(out).toContain('vai.getVertexAI(undefined, { location: vai.AIModel })'); + expect(out).toContain('export const tail = 42;'); + expect(warn).toHaveBeenCalledTimes(1); + }); + + it('leaves a removed symbol in place and warns with guidance', () => { + const { context: spiedContext, warn } = contextWithLogSpies(); + const source = [ + `import { getVertexAI, VertexAIOptions } from '@angular/fire/vertexai';`, + `export let options: VertexAIOptions | undefined;`, + `export const a = getVertexAI();`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, spiedContext, typescript); + + const out = tree.readText('src/app/foo.ts'); + // VertexAIOptions has no drop-in successor, so it keeps its name (a loud break) while the + // rest of the file still migrates. + expect(out).toContain(`import { getAI, VertexAIBackend, VertexAIOptions } from '@angular/fire/ai';`); + expect(out).toContain('let options: VertexAIOptions | undefined;'); + expect(out).toContain('getAI(undefined, { backend: new VertexAIBackend() })'); + expect(warn).toHaveBeenCalledTimes(1); + expect(warn.calls.mostRecent().args[0]).toContain('no longer exists in the new entry point'); + }); + + it('reuses an existing VertexAIBackend import instead of adding a second one', () => { + const source = [ + `import { VertexAIBackend } from '@angular/fire/ai';`, + `import { getVertexAI } from '@angular/fire/vertexai';`, + `export const a = getVertexAI();`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + + const out = tree.readText('src/app/foo.ts'); + expect(out).toContain(`import { VertexAIBackend } from '@angular/fire/ai';`); + expect(out).toContain(`import { getAI } from '@angular/fire/ai';`); + expect(out).toContain('getAI(undefined, { backend: new VertexAIBackend() })'); + // The pre-existing import plus one constructor call: no duplicate binding. + expect((out.match(/VertexAIBackend/g) || []).length).toBe(2); + }); + + it('rewrites a call whose location reads a property that merely shares a rewritten name', () => { + const source = [ + `import { getVertexAI, provideVertexAI } from '@angular/fire/vertexai';`, + `import { getApp } from '@angular/fire/app';`, + `declare const settings: { provideVertexAI: string };`, + `export const p = provideVertexAI(() => 1);`, + `export const a = getVertexAI(getApp(), { location: settings.provideVertexAI });`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + + const out = tree.readText('src/app/foo.ts'); + // `settings.provideVertexAI` is a property NAME, which pass 2 never edits, so there is no + // overlap and the call must still rewrite. + expect(out).toContain('getAI(getApp(), { backend: new VertexAIBackend(settings.provideVertexAI) })'); + expect(out).toContain('provideAI(() => 1)'); + }); + + it('rewrites a call whose options contain a property key sharing a rewritten name', () => { + const source = [ + `import { getVertexAI } from '@angular/fire/vertexai';`, + `import { getApp } from '@angular/fire/app';`, + `declare const build: (value: unknown) => string;`, + `export const a = getVertexAI(getApp(), { location: build({ getVertexAI: true }) });`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + + expect(tree.readText('src/app/foo.ts')) + .toContain('getAI(getApp(), { backend: new VertexAIBackend(build({ getVertexAI: true })) })'); + }); + + it('rewrites a namespace call whose location reads a non-renamed namespace member', () => { + const source = [ + `import * as vai from '@angular/fire/vertexai';`, + `export const a = vai.getVertexAI(undefined, { location: vai.DEFAULT_LOCATION });`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + + // `vai.DEFAULT_LOCATION` gets no edit (DEFAULT_LOCATION is not renamed), so no overlap. + expect(tree.readText('src/app/foo.ts')) + .toContain('vai.getAI(undefined, { backend: new vai.VertexAIBackend(vai.DEFAULT_LOCATION) })'); + }); + + it('warns about a removed symbol reached through a namespace import', () => { + const { context: spiedContext, warn } = contextWithLogSpies(); + const source = [ + `import * as vai from '@angular/fire/vertexai';`, + `export const options: vai.VertexAIOptions = {};`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, spiedContext, typescript); + + expect(tree.readText('src/app/foo.ts')).toContain('vai.VertexAIOptions'); + expect(warn).toHaveBeenCalledTimes(1); + expect(warn.calls.mostRecent().args[0]).toContain('no longer exists in the new entry point'); + }); + + it('rewrites a statement mixing an aliased vertex import, a dropped one, and getAI', () => { + const source = [ + `import { getVertexAI as gv, getVertexAI, getAI } from '@angular/fire/vertexai';`, + `export const c = gv();`, + `export const keep = getAI;`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + const changed = rewriteVertexAIToAI(tree, context, typescript); + + // The backend insertion after `gv` and the removal of the unused unaliased specifier share + // a boundary offset. They compose, and must not trip the conflicting-edits backstop. + expect(changed).toBe(true); + const out = tree.readText('src/app/foo.ts'); + expect(out).toContain(`import { getAI as gv, VertexAIBackend, getAI } from '@angular/fire/ai';`); + expect(out).toContain('gv(undefined, { backend: new VertexAIBackend() })'); + expect(out).toContain('const keep = getAI;'); + }); + + it('tolerates a null project entry in angular.json', () => { + const tree = new HostTree(); + tree.create('angular.json', JSON.stringify({ + projects: { app: { root: '', sourceRoot: 'src' }, broken: null }, + })); + tree.create('src/app/foo.ts', `import { getVertexAI } from '@angular/fire/vertexai';`); + + const changed = rewriteVertexAIToAI(tree, context, typescript); + + expect(changed).toBe(true); + expect(tree.readText('src/app/foo.ts')).toBe(`import { getAI } from '@angular/fire/ai';`); + }); + + it('handles a file with a very deep expression without overflowing', () => { + const source = [ + `import { getVertexAI } from '@angular/fire/vertexai';`, + `export const sum = ${new Array(4000).fill('1').join(' + ')};`, + `export const vertex = getVertexAI();`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + const changed = rewriteVertexAIToAI(tree, context, typescript); + + expect(changed).toBe(true); + expect(tree.readText('src/app/foo.ts')) + .toContain('getAI(undefined, { backend: new VertexAIBackend() })'); + }); + + it('skips a file with syntax errors instead of editing its broken tree, and warns', () => { + const { context: spiedContext, warn } = contextWithLogSpies(); + const broken = [ + `import { getVertexAI } from '@angular/fire/vertexai';`, + `export const v = getVertexAI(`, + `class {`, + ].join('\n'); + const tree = treeWith({ + 'src/app/broken.ts': broken, + 'src/app/good.ts': `import { getVertexAI } from '@angular/fire/vertexai';`, + }); + + rewriteVertexAIToAI(tree, spiedContext, typescript); + + // The broken file is untouched (error-recovered offsets are unreliable), the good one migrates. + expect(tree.readText('src/app/broken.ts')).toBe(broken); + expect(tree.readText('src/app/good.ts')).toBe(`import { getAI } from '@angular/fire/ai';`); + expect(warn.calls.allArgs().map(callArgs => String(callArgs[0])).join('\n')) + .toContain('syntax errors'); + }); + + it('handles a sourceRoot with a trailing slash', () => { + const tree = new HostTree(); + tree.create('angular.json', JSON.stringify({ + projects: { app: { root: '', sourceRoot: 'src/' } }, + })); + tree.create('src/app/foo.ts', `import { getVertexAI } from '@angular/fire/vertexai';`); + + const changed = rewriteVertexAIToAI(tree, context, typescript); + + expect(changed).toBe(true); + expect(tree.readText('src/app/foo.ts')).toBe(`import { getAI } from '@angular/fire/ai';`); + }); + + describe('applyEdits', () => { + + it('applies disjoint edits back to front', () => { + expect(applyEdits('abcdef', [ + { start: 1, end: 2, replacement: 'B' }, + { start: 4, end: 5, replacement: 'E' }, + ])).toBe('aBcdEf'); + }); + + it('accepts an insertion adjacent to a replacement boundary', () => { + expect(applyEdits('abcdef', [ + { start: 1, end: 3, replacement: 'X' }, + { start: 3, end: 3, replacement: '+' }, + ])).toBe('aX+def'); + }); + + it('applies a removal and an insertion sharing a start offset in composing order', () => { + expect(applyEdits('ab, drop, cd', [ + { start: 2, end: 2, replacement: '+X' }, + { start: 2, end: 8, replacement: '' }, + ])).toBe('ab+X, cd'); + }); + + it('throws on overlapping edits instead of corrupting the text', () => { + expect(() => applyEdits('abcdef', [ + { start: 1, end: 4, replacement: 'X' }, + { start: 2, end: 3, replacement: 'Y' }, + ])).toThrowError(/conflicting rewrite edits/); + }); + + }); + + it('is idempotent when re-run on already-migrated code', () => { + const source = [ + `import { provideVertexAI, getVertexAI } from '@angular/fire/vertexai';`, + `export const p = provideVertexAI(() => getVertexAI());`, + ].join('\n'); + const tree = treeWith({ 'src/app/foo.ts': source }); + + rewriteVertexAIToAI(tree, context, typescript); + const afterFirst = tree.readText('src/app/foo.ts'); + const changedAgain = rewriteVertexAIToAI(tree, context, typescript); + + expect(changedAgain).toBe(false); + expect(tree.readText('src/app/foo.ts')).toBe(afterFirst); + }); + +}); diff --git a/src/schematics/update/v21/vertexai-to-ai/ast-walk.ts b/src/schematics/update/v21/vertexai-to-ai/ast-walk.ts new file mode 100644 index 000000000..b5d157860 --- /dev/null +++ b/src/schematics/update/v21/vertexai-to-ai/ast-walk.ts @@ -0,0 +1,27 @@ +// A stack-based depth-first walk over a TypeScript AST. The rewrite runs on user files of +// arbitrary shape, and a recursive walk overflows the call stack on deep expressions (a few +// thousand chained operands), so every full-tree traversal in this package uses this instead. + +import type * as ts from 'typescript'; + +/** + * Visit root and every descendant in source order (pre-order), without recursion. + * + * @param visitNode return false to skip the node's children (subtree pruning). + */ +export const forEachNodeDeep = (compiler: typeof ts, root: ts.Node, visitNode: (node: ts.Node) => boolean | void): void => { + const pending: ts.Node[] = [root]; + while (pending.length > 0) { + const node = pending.pop(); + if (!node || visitNode(node) === false) { + continue; + } + const children: ts.Node[] = []; + compiler.forEachChild(node, child => { + children.push(child); + }); + for (let index = children.length - 1; index >= 0; index--) { + pending.push(children[index]); + } + } +}; diff --git a/src/schematics/update/v21/vertexai-to-ai/compiler.ts b/src/schematics/update/v21/vertexai-to-ai/compiler.ts new file mode 100644 index 000000000..8e8b017f8 --- /dev/null +++ b/src/schematics/update/v21/vertexai-to-ai/compiler.ts @@ -0,0 +1,30 @@ +// How the TypeScript compiler is obtained at ng-update time. It is an optional peer +// dependency, so resolution may fail, and the caller degrades to skip-with-warning +// instead of crashing the migration. + +import { join } from 'path'; +import type * as ts from 'typescript'; + +/** + * Resolve the workspace's `typescript`, or undefined where that is impossible (no `require` in + * this runtime, or the package is not reachable). + */ +export const resolveTypescript = (): typeof ts | undefined => { + if (typeof require !== 'function') { + return undefined; + } + const resolutions: (() => typeof ts)[] = [ + () => require('typescript'), + // The workspace root (ng update's working directory): under an isolated node_modules layout + // the workspace's own typescript may not be reachable from the package itself. + () => require('module').createRequire(join(process.cwd(), 'package.json'))('typescript'), + ]; + for (const resolution of resolutions) { + try { + return resolution(); + } catch { + // Try the next resolution. + } + } + return undefined; +}; diff --git a/src/schematics/update/v21/vertexai-to-ai/index.ts b/src/schematics/update/v21/vertexai-to-ai/index.ts new file mode 100644 index 000000000..413edaf2b --- /dev/null +++ b/src/schematics/update/v21/vertexai-to-ai/index.ts @@ -0,0 +1,255 @@ +// The schematic-facing entry point of the Vertex AI -> AI Logic rewrite: resolves the +// compiler, walks the workspace's project roots, runs the per-file pipeline (safety +// analysis, the two scan passes, the getVertexAI edit builders), applies the edits, and +// logs every rewritten or deliberately skipped site. + +import { posix } from 'path'; +import { SchematicContext, Tree } from '@angular-devkit/schematics'; +import type * as ts from 'typescript'; +import { overwriteIfExists, safeReadJSON } from '../../../common.js'; +import { resolveTypescript } from './compiler.js'; +import { collectUsageEdits, scanDeclarations } from './passes.js'; +import { collectInjectionConflicts, collectShadowedNames } from './safety.js'; +import { MODULE_SPECIFIER_REWRITES, REMOVED_SYMBOL_GUIDANCE } from './tables.js'; +import type { FileContext, FileRewrite, TextEdit, VertexClassification, VertexImport } from './types.js'; +import { buildVertexEdits } from './vertex-edits.js'; + +const UPGRADE_GUIDE = 'see the AngularFire v21 upgrade guide (docs/version-21-upgrade.md)'; + +/** One line of the migration log: a position in the file plus the message for it. */ +interface LogEntry { + level: 'info' | 'warn'; + position: number; + text: string; +} + +/** + * parseDiagnostics is not part of the public SourceFile type but is always populated by + * createSourceFile at runtime. An error-recovered AST carries unreliable offsets, so a file + * with syntax errors must never be edited. + */ +interface ParsedSourceFile { + readonly text: string; + parseDiagnostics?: readonly unknown[]; +} + +/** + * Collect everything needed to move one source file off the old Vertex AI entry points and symbols. + * Runs the safety analyses, then pass 1 (declarations) and pass 2 (usages plus getVertexAI + * classification), then builds the getVertexAI edits from the classification. + * + * @param fileContext the compiler and parsed source file (never mutated). + * @returns the edits to apply plus the positions the migration log reports. + */ +const collectEditsForSourceFile = (fileContext: FileContext): FileRewrite => { + const { sourceFile } = fileContext; + const scan = scanDeclarations(fileContext, collectShadowedNames(fileContext)); + const vertexBindings = new Map(); + for (const vertexImport of scan.vertexImports) { + vertexBindings.set(vertexImport.localName, vertexImport); + } + const vertex: VertexClassification = { supportedCalls: [], unsupported: [] }; + if (scan.renamedLocalBindings.size > 0 || scan.namespaceBindings.size > 0 || vertexBindings.size > 0) { + collectUsageEdits(fileContext, scan, vertexBindings, vertex); + } + for (const specifier of scan.vertexExportSpecifiers) { + vertex.unsupported.push({ + position: specifier.getStart(sourceFile), + reason: 'a re-export of getVertexAI (rewriting it to getAI would silently change its callers\' backend)', + origin: 'export', + }); + } + const injectionConflicts = scan.vertexImports.length > 0 ? collectInjectionConflicts(fileContext) : []; + const vertexEdits = buildVertexEdits(fileContext, scan, vertex, injectionConflicts); + return { + edits: scan.edits.concat(vertexEdits.edits), + vertexCallPositions: vertexEdits.callPositions, + blockedCallPositions: vertexEdits.blockedCallPositions, + unsupportedVertexUsages: vertex.unsupported, + shadowedImports: scan.shadowedImports, + injectionConflicts: vertexEdits.injectionBlocked ? injectionConflicts : [], + starExportPositions: scan.starExportPositions, + removedSymbols: scan.removedSymbols, + }; +}; + +/** + * The log lines one file's rewrite produces: an info per rewritten getVertexAI call and a warn + * per site deliberately left for manual migration. + */ +const rewriteLogEntries = (rewrite: FileRewrite): LogEntry[] => [ + ...rewrite.vertexCallPositions.map((position): LogEntry => ({ + level: 'info', + position, + text: 'rewrote getVertexAI(...) to getAI(..., { backend: new VertexAIBackend(...) }) to keep the call on the Vertex AI backend', + })), + ...rewrite.unsupportedVertexUsages.map((usage): LogEntry => ({ + level: 'warn', + position: usage.position, + text: `left ${usage.reason}. Plain getAI() uses the Gemini Developer API backend, NOT Vertex AI. Migrate this site by hand, ${UPGRADE_GUIDE}`, + })), + ...rewrite.shadowedImports.map((shadow): LogEntry => ({ + level: 'warn', + position: shadow.position, + text: `left the \`${shadow.name}\` import name and its usages unrenamed: this file also declares a local named \`${shadow.name}\`, and name-based rewriting cannot tell the two apart. The import path itself still moves to the new entry point, so the leftover name fails to compile there. Migrate this file by hand, ${UPGRADE_GUIDE}`, + })), + ...rewrite.injectionConflicts.map((conflict): LogEntry => ({ + level: 'warn', + position: conflict.position, + text: `\`${conflict.name}\` is already bound here from a source other than AI Logic, so the backend-preserving rewrite cannot inject or reuse it. The file's getVertexAI code was left as is, and its import path still moves to the new entry point, so the leftover getVertexAI fails to compile there. Migrate it by hand, ${UPGRADE_GUIDE}`, + })), + ...rewrite.blockedCallPositions.map((position): LogEntry => ({ + level: 'warn', + position, + text: rewrite.injectionConflicts.length > 0 + ? 'left a rewritable getVertexAI call unrewritten because this file binds getAI or VertexAIBackend from another source (see that warning). Every use of the file\'s named getVertexAI imports is kept together so the pieces stay consistent, migrate them by hand' + : 'left a rewritable getVertexAI call unrewritten because another getVertexAI use in this file cannot be rewritten (see its own warning). Every use of the file\'s named getVertexAI imports is kept together so the pieces stay consistent, migrate them by hand', + })), + ...rewrite.starExportPositions.map((position): LogEntry => ({ + level: 'warn', + position, + text: `left \`export *\` from an old Vertex AI entry point unmigrated: rewriting it would silently rename this file's re-exported public symbols. Re-export what you need by name from '@angular/fire/ai' instead, ${UPGRADE_GUIDE}`, + })), + ...rewrite.removedSymbols.map((removed): LogEntry => ({ + level: 'warn', + position: removed.position, + text: `left \`${removed.name}\`, which no longer exists in the new entry point (${REMOVED_SYMBOL_GUIDANCE[removed.name]}). The leftover name fails to compile, migrate it by hand, ${UPGRADE_GUIDE}`, + })), +]; + +/** + * Run the rewrite pipeline over one file: parse, collect edits, log the outcome, apply. + * + * @returns true when the file changed. + */ +const rewriteFile = (host: Tree, context: SchematicContext, compiler: typeof ts, filePath: string, content: string): boolean => { + const fileContext: FileContext = { + compiler, + sourceFile: compiler.createSourceFile(filePath, content, compiler.ScriptTarget.Latest, true), + }; + const parsedSourceFile: ParsedSourceFile = fileContext.sourceFile; + if (parsedSourceFile.parseDiagnostics && parsedSourceFile.parseDiagnostics.length > 0) { + context.logger.warn( + `${filePath}: skipped, the file has syntax errors, so the Vertex AI -> AI Logic rewrite cannot run on it safely. Fix the syntax and re-run ng update, or migrate it by hand, ${UPGRADE_GUIDE}` + ); + return false; + } + const rewrite = collectEditsForSourceFile(fileContext); + for (const entry of rewriteLogEntries(rewrite)) { + const line = fileContext.sourceFile.getLineAndCharacterOfPosition(entry.position).line + 1; + context.logger[entry.level](`${filePath}:${line}: ${entry.text}`); + } + if (rewrite.edits.length === 0) { + return false; + } + let newContent: string; + try { + newContent = applyEdits(content, rewrite.edits); + } catch (error) { + // The classifiers are meant to keep edits disjoint, so reaching this means a bug. Leaving + // the file untouched beats corrupting it. + context.logger.warn(`${filePath}: the Vertex AI -> AI Logic rewrite was skipped for this file (${error}). Migrate it by hand, ${UPGRADE_GUIDE}`); + return false; + } + if (newContent === content) { + return false; + } + overwriteIfExists(host, filePath, newContent); + return true; +}; + +/** + * Apply edits to the source text, back to front so earlier offsets stay valid. + * + * @param content the original file text. + * @param edits the edits to apply. Overlapping spans would slice at stale offsets and corrupt + * the output, so they throw instead. + * @returns the rewritten text. + */ +export const applyEdits = (content: string, edits: TextEdit[]): string => { + // At equal starts a span must apply before a zero-width insertion at that offset: the pair + // composes (remove the span, then insert at its former start), while the reverse order would + // slice the inserted text. + const sorted = edits.slice().sort((a, b) => b.start - a.start || b.end - a.end); + for (let index = 1; index < sorted.length; index++) { + if (sorted[index].end > sorted[index - 1].start) { + throw new Error(`conflicting rewrite edits at offsets ${sorted[index].start} and ${sorted[index - 1].start}`); + } + } + return sorted.reduce((text, edit) => text.slice(0, edit.start) + edit.replacement + text.slice(edit.end), content); +}; + +/** + * The workspace-relative source roots to migrate, from angular.json's projects. + * + * `sourceRoot` is already workspace-relative and includes the project root, so it is used + * directly (falling back to `root`). Joining both would double-count the prefix. A root of '' + * is the workspace itself (older CLI versions generate that for the root project) and must + * survive to become '/', not be dropped. Tree paths are always posix, so the roots join with + * posix separators regardless of the host OS. + */ +const collectSourceRoots = (angularJson: any): string[] => + Object.values(angularJson.projects) + .map((project: any) => project?.sourceRoot || project?.root) + .filter((base: any) => typeof base === 'string') + .map((base: string) => { + const joined = posix.join('/', base); + // posix.join keeps a trailing slash ('src/' becomes '/src/'), which would defeat the + // prefix test in shouldVisit and silently skip the project. + return joined === '/' ? joined : joined.replace(/\/+$/, ''); + }); + +/** + * Whether a tree path is a TypeScript source file under one of the source roots. Installed + * dependencies are excluded: a '/' source root would otherwise pull them in. + */ +const shouldVisit = (filePath: string, srcRoots: string[]): boolean => + filePath.endsWith('.ts') && + !filePath.endsWith('.d.ts') && + !filePath.split('/').includes('node_modules') && + srcRoots.some(root => root === '/' || filePath === root || filePath.startsWith(root + '/')); + +/** + * `ng update` migration step: rewrite a workspace's Vertex AI imports and usages onto Firebase AI + * Logic. Visits the TypeScript files under each project's source root and edits any that import from + * an old entry point. getVertexAI calls keep their backend: they become + * `getAI(app, { backend: new VertexAIBackend(location?) })`, and every rewritten or skipped + * getVertexAI site is logged. + * + * @param compiler the TypeScript compiler to parse with. Defaults to resolving the workspace's + * `typescript` (an optional peer dependency). Callers in environments without `require` + * pass their own. + * @returns true if any file was rewritten. + */ +export const rewriteVertexAIToAI = (host: Tree, context: SchematicContext, compiler?: typeof ts): boolean => { + const resolvedCompiler = compiler ?? resolveTypescript(); + const angularJson = host.exists('angular.json') && safeReadJSON('angular.json', host); + if (!angularJson?.projects) { + return false; + } + const srcRoots = collectSourceRoots(angularJson); + if (srcRoots.length === 0) { + return false; + } + + let changed = false; + host.visit(filePath => { + if (!shouldVisit(filePath, srcRoots)) { + return; + } + const content = host.read(filePath)?.toString(); + if (!content || !Object.keys(MODULE_SPECIFIER_REWRITES).some(specifier => content.includes(specifier))) { + return; + } + if (!resolvedCompiler) { + context.logger.warn( + `${filePath} imports a removed Vertex AI entry point, but the Vertex AI -> AI Logic rewrite was skipped: ` + + 'the `typescript` package could not be resolved (it is an optional peer dependency of @angular/fire). ' + + 'Install typescript and re-run, or migrate by hand - see the v21 upgrade guide (docs/version-21-upgrade.md).' + ); + return; + } + changed = rewriteFile(host, context, resolvedCompiler, filePath, content) || changed; + }); + return changed; +}; diff --git a/src/schematics/update/v21/vertexai-to-ai/passes.ts b/src/schematics/update/v21/vertexai-to-ai/passes.ts new file mode 100644 index 000000000..aa2c9e43f --- /dev/null +++ b/src/schematics/update/v21/vertexai-to-ai/passes.ts @@ -0,0 +1,518 @@ +// The two per-file scan passes. Pass 1 walks the top-level import/export declarations, edits +// module specifiers and renamed symbols, and records the bindings in play. Pass 2 walks the +// whole tree, rewrites usages of renamed bindings, and classifies every getVertexAI reference +// for the backend-preserving call rewrite built in vertex-edits.ts. + +import type * as ts from 'typescript'; +import { forEachNodeDeep } from './ast-walk.js'; +import { GET_VERTEX_AI, MODULE_SPECIFIER_REWRITES, REMOVED_SYMBOL_GUIDANCE, SYMBOL_RENAMES } from './tables.js'; +import type { DeclarationScan, FileContext, RemovedSymbol, SupportedVertexCall, TextEdit, VertexClassification, VertexImport } from './types.js'; + +/** The binding groups pass 2 edits by name, used to detect edit sites inside an options literal. */ +interface EditableBindings { + renamedNames: Set; + namespaceNames: Set; + vertexNames: Set; +} + +/** How one getVertexAI options argument maps onto the backend rewrite. */ +interface OptionsClassification { + supported: boolean; + /** The `location` property's source text, when present. */ + locationText?: string; + /** True when the literal contains an edit site pass 2 would also rewrite (see containsEditSite). */ + conflicting?: boolean; +} + +/** + * Pass 1: rewrite the module specifier and renamed symbols of every import/export declaration that + * pulls from an old Vertex AI entry point, and record the bindings whose usages pass 2 must rewrite. + * + * @param fileContext the compiler and parsed source file. + * @param shadowedNames tracked local names a local declaration shadows (see safety.ts). + * @returns the accumulated edits plus the binding maps for pass 2. + */ +export const scanDeclarations = (fileContext: FileContext, shadowedNames: Set): DeclarationScan => { + const { sourceFile } = fileContext; + const scan: DeclarationScan = { + edits: [], + renamedLocalBindings: new Map(), + namespaceBindings: new Set(), + editedSpecifierTokenStarts: new Set(), + vertexImports: [], + vertexExportSpecifiers: [], + shadowedImports: [], + starExportPositions: [], + removedSymbols: [], + }; + for (const statement of sourceFile.statements) { + collectDeclarationEdits(statement, fileContext, scan, shadowedNames); + } + return scan; +}; + +/** + * Handle one top-level statement in pass 1. A no-op unless the statement imports or exports from an + * old Vertex AI entry point, in which case it edits the module specifier and dispatches its bindings. + * + * @param statement the top-level statement to inspect. + * @param fileContext the compiler and parsed source file (for token offsets). + * @param scan accumulator mutated in place. + * @param shadowedNames tracked local names a local declaration shadows. + */ +const collectDeclarationEdits = (statement: ts.Statement, fileContext: FileContext, scan: DeclarationScan, shadowedNames: Set): void => { + const { compiler: tsc, sourceFile } = fileContext; + const isImport = tsc.isImportDeclaration(statement); + const isExport = tsc.isExportDeclaration(statement); + if (!isImport && !isExport) { + return; + } + const moduleSpecifier = statement.moduleSpecifier; + const newModuleSpecifier = moduleSpecifier && tsc.isStringLiteral(moduleSpecifier) && MODULE_SPECIFIER_REWRITES[moduleSpecifier.text]; + if (!newModuleSpecifier) { + return; + } + + // `export * from ''` (and `export * as ns from`) re-exports the module's symbols + // as the file's own public API. Rewriting the specifier would silently rename those public + // symbols, so the statement is left whole (it fails loudly against v21) and warned about. + if (isExport && (!statement.exportClause || tsc.isNamespaceExport(statement.exportClause))) { + scan.starExportPositions.push(statement.getStart(sourceFile)); + return; + } + + // Rewrite the module specifier text, preserving the surrounding quotes. + scan.edits.push({ + start: moduleSpecifier.getStart(sourceFile) + 1, + end: moduleSpecifier.getEnd() - 1, + replacement: newModuleSpecifier, + }); + + const namedBindings = isImport ? statement.importClause?.namedBindings : statement.exportClause; + if (namedBindings && tsc.isNamespaceImport(namedBindings)) { + if (shadowedNames.has(namedBindings.name.text)) { + scan.shadowedImports.push({ position: namedBindings.getStart(sourceFile), name: namedBindings.name.text }); + } else { + scan.namespaceBindings.add(namedBindings.name.text); + } + return; + } + if (namedBindings && (tsc.isNamedImports(namedBindings) || tsc.isNamedExports(namedBindings))) { + for (const element of namedBindings.elements) { + collectSpecifierEdit(element, statement, fileContext, scan, shadowedNames); + } + } +}; + +/** + * Rename a renamed symbol inside one named import/export specifier, and, for an un-aliased import, + * record its local binding so pass 2 rewrites that binding's usages. getVertexAI specifiers are only + * recorded here, and their edits depend on how the binding is used. + * + * @param element the `{ x }` or `{ x as y }` specifier. + * @param statement the import/export declaration the specifier belongs to. + * @param fileContext the compiler and parsed source file (for token offsets). + * @param scan accumulator mutated in place. + * @param shadowedNames tracked local names a local declaration shadows. + */ +const collectSpecifierEdit = ( + element: ts.ImportSpecifier | ts.ExportSpecifier, + statement: ts.Statement, + fileContext: FileContext, + scan: DeclarationScan, + shadowedNames: Set, +): void => { + const { compiler: tsc, sourceFile } = fileContext; + const importedNameNode = element.propertyName ?? element.name; + // A shadowed local name makes the usage pass unsafe for this binding: exclude it whole. + if (tsc.isImportSpecifier(element) && shadowedNames.has(element.name.text)) { + scan.shadowedImports.push({ position: element.getStart(sourceFile), name: element.name.text }); + return; + } + if (importedNameNode.text === GET_VERTEX_AI) { + if (tsc.isImportSpecifier(element) && tsc.isImportDeclaration(statement)) { + scan.vertexImports.push({ element, statement, localName: element.name.text }); + } else if (tsc.isExportSpecifier(element)) { + scan.vertexExportSpecifiers.push(element); + } + return; + } + // A removed symbol has no drop-in successor: leave it (loud break) and warn with guidance. + if (REMOVED_SYMBOL_GUIDANCE[importedNameNode.text] !== undefined) { + scan.removedSymbols.push({ position: importedNameNode.getStart(sourceFile), name: importedNameNode.text }); + return; + } + const newName = SYMBOL_RENAMES[importedNameNode.text]; + if (!newName) { + return; + } + // An un-aliased `export { VertexAI } from '...'` names the file's OWN public export: rename only + // the target and keep the public name via an alias, mirroring the bare local re-export case in + // identifierUsageEdit. `export { VertexAI as Foo } from '...'` already keeps its public name. + const keepPublicName = tsc.isExportSpecifier(element) && !element.propertyName; + scan.edits.push({ + start: importedNameNode.getStart(sourceFile), + end: importedNameNode.getEnd(), + replacement: keepPublicName ? `${newName} as ${importedNameNode.text}` : newName, + }); + scan.editedSpecifierTokenStarts.add(importedNameNode.getStart(sourceFile)); + if (tsc.isImportSpecifier(element) && !element.propertyName) { + scan.renamedLocalBindings.set(element.name.text, newName); + } +}; + +/** + * Pass 2: walk the whole tree, rewrite usages of the bindings found in pass 1 (renamed local + * identifiers and `ns.oldSymbol` member accesses), and classify every getVertexAI reference. + * + * @param fileContext the compiler and parsed source file. + * @param scan the pass-1 result, whose `edits` is extended in place. + * @param vertexBindings local names bound to getVertexAI by a named import. + * @param vertex classification accumulator mutated in place. + */ +export const collectUsageEdits = ( + fileContext: FileContext, + scan: DeclarationScan, + vertexBindings: Map, + vertex: VertexClassification, +): void => { + const { compiler: tsc, sourceFile } = fileContext; + // The binding groups pass 2 edits by name. A getVertexAI options literal containing an actual + // edit site cannot be replaced as a single span (the inner edit would overlap), so + // classification rejects those calls (see containsEditSite). + const editableBindings: EditableBindings = { + renamedNames: new Set(scan.renamedLocalBindings.keys()), + namespaceNames: scan.namespaceBindings, + vertexNames: new Set(vertexBindings.keys()), + }; + forEachNodeDeep(tsc, sourceFile, node => { + const namespaceEdit = namespaceMemberEdit(node, scan.namespaceBindings, fileContext); + if (namespaceEdit) { + scan.edits.push(namespaceEdit); + } + const removedNamespaceMember = namespaceRemovedSymbol(node, scan.namespaceBindings, fileContext); + if (removedNamespaceMember) { + scan.removedSymbols.push(removedNamespaceMember); + } + classifyNamespaceVertexAccess(node, scan.namespaceBindings, fileContext, vertex, editableBindings); + if (tsc.isIdentifier(node)) { + classifyVertexIdentifier(node, vertexBindings, fileContext, vertex, editableBindings); + const usageEdit = identifierUsageEdit(node, scan, fileContext); + if (usageEdit) { + scan.edits.push(usageEdit); + } + } + }); +}; + +/** + * A `ns.` access, in value or type position. No rename exists for it, so the + * site is left as is (a loud break against the new entry point) and warned with guidance. + */ +const namespaceRemovedSymbol = (node: ts.Node, namespaceBindings: Set, fileContext: FileContext): RemovedSymbol | undefined => { + const { compiler: tsc, sourceFile } = fileContext; + if ( + tsc.isPropertyAccessExpression(node) && + tsc.isIdentifier(node.expression) && + namespaceBindings.has(node.expression.text) && + REMOVED_SYMBOL_GUIDANCE[node.name.text] !== undefined + ) { + return { position: node.name.getStart(sourceFile), name: node.name.text }; + } + if ( + tsc.isQualifiedName(node) && + tsc.isIdentifier(node.left) && + namespaceBindings.has(node.left.text) && + REMOVED_SYMBOL_GUIDANCE[node.right.text] !== undefined + ) { + return { position: node.right.getStart(sourceFile), name: node.right.text }; + } + return undefined; +}; + +/** + * Classify a `ns.getVertexAI` member access: a direct call gets the backend-preserving rewrite, + * anything else is left in place and logged. + */ +const classifyNamespaceVertexAccess = ( + node: ts.Node, + namespaceBindings: Set, + fileContext: FileContext, + vertex: VertexClassification, + editableBindings: EditableBindings, +): void => { + const { compiler: tsc, sourceFile } = fileContext; + if ( + !tsc.isPropertyAccessExpression(node) || + !tsc.isIdentifier(node.expression) || + !namespaceBindings.has(node.expression.text) || + node.name.text !== GET_VERTEX_AI + ) { + return; + } + const parent = node.parent; + if (tsc.isCallExpression(parent) && parent.expression === node) { + classifyVertexCall(parent, { namespaceAccess: node }, fileContext, vertex, 'namespace', editableBindings); + return; + } + vertex.unsupported.push({ + position: node.getStart(sourceFile), + reason: `a use of ${node.expression.text}.getVertexAI that is not a direct call`, + origin: 'namespace', + }); +}; + +/** + * Classify one identifier reference to a named getVertexAI import: look-alikes are ignored, a + * direct call gets the backend-preserving rewrite, and any other value reference is left in + * place and logged. + */ +const classifyVertexIdentifier = ( + node: ts.Identifier, + vertexBindings: Map, + fileContext: FileContext, + vertex: VertexClassification, + editableBindings: EditableBindings, +): void => { + const { compiler: tsc, sourceFile } = fileContext; + const binding = vertexBindings.get(node.text); + if (!binding || isNonUsageReference(fileContext, node)) { + return; + } + const parent = node.parent; + // A direct call is the one rewritable shape. + if (tsc.isCallExpression(parent) && parent.expression === node) { + classifyVertexCall(parent, { binding }, fileContext, vertex, 'binding', editableBindings); + return; + } + // Everything else hands the function itself around, where a rename would change backends. + vertex.unsupported.push({ + position: node.getStart(sourceFile), + reason: tsc.isExportSpecifier(parent) + ? 'a re-export of getVertexAI (rewriting it to getAI would silently change its callers\' backend)' + : 'a use of getVertexAI that is not a direct call (only direct calls can be rewritten safely)', + origin: 'binding', + }); +}; + +/** + * Whether pass 2 would actually edit this identifier. Mirrors the passes' own position rules: + * look-alikes (member-access names, property keys, declared names) are never edited, a renamed + * or getVertexAI binding in a value position is, and a namespace name matters only as the left + * side of a member access whose member gets renamed or rewritten. + */ +const producesEdit = (node: ts.Identifier, fileContext: FileContext, editableBindings: EditableBindings): boolean => { + const { compiler: tsc } = fileContext; + if (isNonUsageReference(fileContext, node)) { + return false; + } + if (editableBindings.renamedNames.has(node.text) || editableBindings.vertexNames.has(node.text)) { + return true; + } + if (!editableBindings.namespaceNames.has(node.text)) { + return false; + } + const parent = node.parent; + if (tsc.isPropertyAccessExpression(parent) && parent.expression === node) { + return SYMBOL_RENAMES[parent.name.text] !== undefined || parent.name.text === GET_VERTEX_AI; + } + if (tsc.isQualifiedName(parent) && parent.left === node) { + return SYMBOL_RENAMES[parent.right.text] !== undefined; + } + return false; +}; + +/** + * Whether a subtree contains an identifier that pass 2 would also rewrite. + */ +const containsEditSite = (root: ts.Node, fileContext: FileContext, editableBindings: EditableBindings): boolean => { + const { compiler: tsc } = fileContext; + let found = false; + forEachNodeDeep(tsc, root, node => { + if (found) { + return false; + } + if (tsc.isIdentifier(node) && producesEdit(node, fileContext, editableBindings)) { + found = true; + return false; + } + }); + return found; +}; + +/** + * Whether an options argument is a literal the rewrite understands: an empty object literal, or + * one whose only property is `location` (in either assignment or shorthand form). + */ +const classifyOptionsArgument = (optionsArgument: ts.Expression, fileContext: FileContext, editableBindings: EditableBindings): OptionsClassification => { + const { compiler: tsc, sourceFile } = fileContext; + if (!tsc.isObjectLiteralExpression(optionsArgument)) { + return { supported: false }; + } + // The options literal is replaced as ONE span, so an edit site inside it would produce + // overlapping edits. Such calls are left for manual migration. + if (containsEditSite(optionsArgument, fileContext, editableBindings)) { + return { supported: false, conflicting: true }; + } + if (optionsArgument.properties.length === 0) { + return { supported: true }; + } + const soleProperty = optionsArgument.properties.length === 1 ? optionsArgument.properties[0] : undefined; + if ( + soleProperty && + tsc.isPropertyAssignment(soleProperty) && + tsc.isIdentifier(soleProperty.name) && + soleProperty.name.text === 'location' + ) { + return { supported: true, locationText: soleProperty.initializer.getText(sourceFile) }; + } + if (soleProperty && tsc.isShorthandPropertyAssignment(soleProperty) && soleProperty.name.text === 'location') { + return { supported: true, locationText: 'location' }; + } + return { supported: false }; +}; + +/** + * Decide whether one getVertexAI call's argument shape can be rewritten to + * `getAI(app, { backend: new VertexAIBackend(location?) })` without guessing. + * + * Supported shapes: no arguments, an app argument alone, and an app argument plus an object + * literal whose only property is `location` (or an empty literal). Anything else (a spread, a + * variable holding the options, extra option keys) is left in place and logged. + */ +const classifyVertexCall = ( + call: ts.CallExpression, + target: Pick, + fileContext: FileContext, + vertex: VertexClassification, + origin: 'binding' | 'namespace', + editableBindings: EditableBindings, +): void => { + const { compiler: tsc, sourceFile } = fileContext; + const callArguments = call.arguments; + const hasSpread = callArguments.some(tsc.isSpreadElement); + // Zero arguments or a lone app argument: nothing needs translating. + if (callArguments.length <= 1 && !hasSpread) { + vertex.supportedCalls.push({ call, ...target }); + return; + } + // An app argument plus a literal options object: the location moves into the backend. + let conflicting = false; + if (callArguments.length === 2 && !hasSpread) { + const options = classifyOptionsArgument(callArguments[1], fileContext, editableBindings); + if (options.supported) { + vertex.supportedCalls.push({ call, ...target, locationText: options.locationText }); + return; + } + conflicting = options.conflicting === true; + } + vertex.unsupported.push({ + position: call.getStart(sourceFile), + reason: conflicting + ? 'a getVertexAI call whose options mention other symbols this migration rewrites (replacing both at once would conflict)' + : 'a getVertexAI call whose arguments do not map onto getAI safely (only an optional app argument plus an optional literal `{ location }` object are rewritten)', + origin, + }); +}; + +/** + * Build the edit for a namespaced `ns.oldSymbol` access, in value position (a PropertyAccessExpression + * like `ns.provideVertexAI(...)`) or type position (a QualifiedName like `let x: ns.VertexAI`). + * + * @returns the edit, or undefined when the node is not a renamable namespace access. + */ +const namespaceMemberEdit = (node: ts.Node, namespaceBindings: Set, fileContext: FileContext): TextEdit | undefined => { + const { compiler: tsc, sourceFile } = fileContext; + if ( + tsc.isPropertyAccessExpression(node) && + tsc.isIdentifier(node.expression) && + namespaceBindings.has(node.expression.text) && + SYMBOL_RENAMES[node.name.text] + ) { + return { start: node.name.getStart(sourceFile), end: node.name.getEnd(), replacement: SYMBOL_RENAMES[node.name.text] }; + } + if ( + tsc.isQualifiedName(node) && + tsc.isIdentifier(node.left) && + namespaceBindings.has(node.left.text) && + SYMBOL_RENAMES[node.right.text] + ) { + return { start: node.right.getStart(sourceFile), end: node.right.getEnd(), replacement: SYMBOL_RENAMES[node.right.text] }; + } + return undefined; +}; + +/** + * Build the edit for an identifier that references an un-aliased renamed import. Returns undefined + * for look-alikes that are not references to the import. + * + * @returns the edit, or undefined when the identifier should be left as is. + */ +const identifierUsageEdit = (node: ts.Identifier, scan: DeclarationScan, fileContext: FileContext): TextEdit | undefined => { + const { compiler: tsc, sourceFile } = fileContext; + const replacement = scan.renamedLocalBindings.get(node.text); + if (replacement === undefined) { + return undefined; + } + const start = node.getStart(sourceFile); + // Already renamed in pass 1, or not a reference to the import at all. + if (scan.editedSpecifierTokenStarts.has(start) || isNonUsageReference(fileContext, node)) { + return undefined; + } + const parent = node.parent; + // A bare local re-export `export { VertexAI }` (no `from`) is a usage of the renamed local + // binding. Preserve the external export name by expanding to `export { AI as VertexAI }`. An + // `export { x } from '...'` specifier is instead handled in pass 1. + if (tsc.isExportSpecifier(parent) && parent.name === node && !parent.propertyName) { + const exportDeclaration = parent.parent.parent; + if (tsc.isExportDeclaration(exportDeclaration) && !exportDeclaration.moduleSpecifier) { + return { start, end: node.getEnd(), replacement: `${replacement} as ${node.text}` }; + } + return undefined; + } + // `{ provideVertexAI }` is shorthand for `{ provideVertexAI: provideVertexAI }`. Only the value + // changed, so expand it rather than rename the key. + if (tsc.isShorthandPropertyAssignment(parent) && parent.name === node) { + return { start: node.getEnd(), end: node.getEnd(), replacement: `: ${replacement}` }; + } + return { start, end: node.getEnd(), replacement }; +}; + +/** + * Whether an identifier is not a reference to an imported binding at all: the import specifier + * itself (handled in pass 1), a destructuring key or shorthand binding target (they read a + * property, not the import, while a binding initializer like `{ cb = getVertexAI }` is a real + * usage), or a member-access or declared-property name (see isMemberOrDeclaredName). + */ +const isNonUsageReference = (fileContext: FileContext, node: ts.Identifier): boolean => { + const { compiler: tsc } = fileContext; + const parent = node.parent; + if (tsc.isImportSpecifier(parent) && (parent.propertyName === node || parent.name === node)) { + return true; + } + if (tsc.isBindingElement(parent) && (parent.propertyName === node || (parent.name === node && !parent.propertyName))) { + return true; + } + return isMemberOrDeclaredName(fileContext, node, parent); +}; + +/** + * Whether an identifier is a member-access name (`obj.getVertexAI`) or a declared + * property/accessor/enum-member name rather than a value reference to the import. + */ +const isMemberOrDeclaredName = (fileContext: FileContext, node: ts.Identifier, parent: ts.Node): boolean => { + const { compiler: tsc } = fileContext; + const isMemberName = + (tsc.isPropertyAccessExpression(parent) && parent.name === node) || + (tsc.isQualifiedName(parent) && parent.right === node); + const isDeclaredPropertyName = + (tsc.isPropertyAssignment(parent) || + tsc.isPropertyDeclaration(parent) || + tsc.isPropertySignature(parent) || + tsc.isMethodDeclaration(parent) || + tsc.isGetAccessorDeclaration(parent) || + tsc.isSetAccessorDeclaration(parent) || + tsc.isEnumMember(parent)) && + parent.name === node; + return isMemberName || isDeclaredPropertyName; +}; diff --git a/src/schematics/update/v21/vertexai-to-ai/safety.ts b/src/schematics/update/v21/vertexai-to-ai/safety.ts new file mode 100644 index 000000000..020da17f3 --- /dev/null +++ b/src/schematics/update/v21/vertexai-to-ai/safety.ts @@ -0,0 +1,156 @@ +// The analyses that decide when rewriting is NOT safe: local declarations that shadow a +// tracked import name, and bindings of the injected names (getAI, VertexAIBackend) that come +// from somewhere other than AI Logic. Both matter because the passes match identifiers by +// text without scope analysis, and the design rule is to leave code whole and warn rather +// than ever change behavior silently. + +import type * as ts from 'typescript'; +import { forEachNodeDeep } from './ast-walk.js'; +import { AI_MODULE_SPECIFIERS, BACKEND_CLASS, GET_AI, GET_VERTEX_AI, MODULE_SPECIFIER_REWRITES, SYMBOL_RENAMES } from './tables.js'; +import type { FileContext, ShadowedImport } from './types.js'; + +/** + * The import/export statements that reference an old Vertex AI entry point. + */ +const oldModuleStatementsOf = (fileContext: FileContext): Set => { + const { compiler: tsc, sourceFile } = fileContext; + const statements = new Set(); + for (const statement of sourceFile.statements) { + if ( + (tsc.isImportDeclaration(statement) || tsc.isExportDeclaration(statement)) && + statement.moduleSpecifier && + tsc.isStringLiteral(statement.moduleSpecifier) && + MODULE_SPECIFIER_REWRITES[statement.moduleSpecifier.text] + ) { + statements.add(statement); + } + } + return statements; +}; + +/** + * The local names bound by the old-module imports whose usages the passes rewrite BY NAME: + * un-aliased renamed imports, every getVertexAI import (aliased or not, its calls are rewritten), + * and namespace imports. Aliased renames are absent: their usages are never edited. + */ +const collectTrackedLocalNames = (fileContext: FileContext, oldModuleStatements: Set): Set => { + const { compiler: tsc } = fileContext; + const names = new Set(); + for (const statement of oldModuleStatements) { + if (!tsc.isImportDeclaration(statement)) { + continue; + } + const namedBindings = statement.importClause?.namedBindings; + if (!namedBindings) { + continue; + } + if (tsc.isNamespaceImport(namedBindings)) { + names.add(namedBindings.name.text); + continue; + } + for (const element of namedBindings.elements) { + const importedName = (element.propertyName ?? element.name).text; + if (importedName === GET_VERTEX_AI || (SYMBOL_RENAMES[importedName] && !element.propertyName)) { + names.add(element.name.text); + } + } + } + return names; +}; + +/** + * The local name a node declares, for shadow detection. Lexical declarations only: class members + * and object properties do not shadow imports. + */ +const declaredName = (fileContext: FileContext, node: ts.Node): string | undefined => { + const { compiler: tsc } = fileContext; + if ((tsc.isVariableDeclaration(node) || tsc.isParameter(node) || tsc.isBindingElement(node)) && tsc.isIdentifier(node.name)) { + return node.name.text; + } + if ( + (tsc.isFunctionDeclaration(node) || + tsc.isFunctionExpression(node) || + tsc.isClassDeclaration(node) || + tsc.isClassExpression(node) || + tsc.isInterfaceDeclaration(node) || + tsc.isTypeAliasDeclaration(node) || + tsc.isEnumDeclaration(node)) && + node.name + ) { + return node.name.text; + } + if (tsc.isTypeParameterDeclaration(node)) { + return node.name.text; + } + if (tsc.isImportSpecifier(node) || tsc.isNamespaceImport(node)) { + return node.name.text; + } + if (tsc.isImportClause(node) && node.name) { + return node.name.text; + } + return undefined; +}; + +/** + * Whether an import binding is the AI Logic module's own export under its own name, making it + * safe for rewritten getVertexAI calls to reuse. Anything else bound to an injected name (an + * aliased or default import, a namespace import, or an import from an unrelated module) is a + * conflict. + */ +const isReusableAIImport = (fileContext: FileContext, node: ts.Node): boolean => { + const { compiler: tsc } = fileContext; + if (!tsc.isImportSpecifier(node) || node.propertyName) { + return false; + } + let current: ts.Node | undefined = node; + while (current && !tsc.isImportDeclaration(current)) { + current = current.parent; + } + if (!current || !tsc.isImportDeclaration(current)) { + return false; + } + return tsc.isStringLiteral(current.moduleSpecifier) && AI_MODULE_SPECIFIERS.has(current.moduleSpecifier.text); +}; + +/** + * Which tracked names are also declared locally somewhere outside the old-module imports. The + * usage passes match identifiers by text without scope analysis, so any such name is unsafe to + * rewrite (a shadowed local usage would be redirected to the import) and gets excluded instead. + */ +export const collectShadowedNames = (fileContext: FileContext): Set => { + const { compiler: tsc, sourceFile } = fileContext; + const oldModuleStatements = oldModuleStatementsOf(fileContext); + const trackedNames = collectTrackedLocalNames(fileContext, oldModuleStatements); + const shadowed = new Set(); + if (trackedNames.size === 0) { + return shadowed; + } + forEachNodeDeep(tsc, sourceFile, node => { + if (oldModuleStatements.has(node)) { + return false; + } + const declared = declaredName(fileContext, node); + if (declared && trackedNames.has(declared)) { + shadowed.add(declared); + } + }); + return shadowed; +}; + +/** + * Bindings of getAI or VertexAIBackend that do NOT come from an AI Logic module: a lexical + * declaration, or any other import shape. The rewrite injects references to these names, so a + * foreign binding would capture the rewritten calls or collide with the injected import. The + * file's getVertexAI imports are left unmigrated instead. + */ +export const collectInjectionConflicts = (fileContext: FileContext): ShadowedImport[] => { + const { compiler: tsc, sourceFile } = fileContext; + const conflicts: ShadowedImport[] = []; + forEachNodeDeep(tsc, sourceFile, node => { + const declared = declaredName(fileContext, node); + if ((declared === GET_AI || declared === BACKEND_CLASS) && !isReusableAIImport(fileContext, node)) { + conflicts.push({ position: node.getStart(sourceFile), name: declared }); + } + }); + return conflicts; +}; diff --git a/src/schematics/update/v21/vertexai-to-ai/tables.ts b/src/schematics/update/v21/vertexai-to-ai/tables.ts new file mode 100644 index 000000000..7e125e52e --- /dev/null +++ b/src/schematics/update/v21/vertexai-to-ai/tables.ts @@ -0,0 +1,44 @@ +// The data tables that define the Vertex AI -> AI Logic migration surface: which module +// specifiers move where, which symbols are one-to-one renames, and which names the call +// rewrite injects. Pure data, no logic. + +// Both the AngularFire entry points and the firebase SDK's own entry points are rewritten: this +// migration also moves the workspace to firebase 12, where `firebase/vertexai` is gone. +export const MODULE_SPECIFIER_REWRITES: Record = { + '@angular/fire/vertexai': '@angular/fire/ai', + '@angular/fire/vertexai-preview': '@angular/fire/ai', + 'firebase/vertexai': 'firebase/ai', + 'firebase/vertexai-preview': 'firebase/ai', +}; + +// Straight one-to-one renames. getVertexAI is deliberately absent: getAI and getVertexAI +// coexisted in the old modules and default to DIFFERENT backends (plain getAI() talks to the +// Gemini Developer API), so getVertexAI gets a backend-preserving call rewrite instead: see +// vertex-edits.ts. +export const SYMBOL_RENAMES: Record = { + provideVertexAI: 'provideAI', + VertexAI: 'AI', + VertexAIError: 'AIError', + VertexAIErrorCode: 'AIErrorCode', + VertexAIModel: 'AIModel', + VertexAIInstances: 'AIInstances', + vertexAIInstance$: 'AIInstance$', + VertexAIModule: 'AIModule', +}; + +// Old exports that were removed rather than renamed: their successor is not a drop-in +// replacement, so the import keeps its name (breaking loudly) and the log explains why. +export const REMOVED_SYMBOL_GUIDANCE: Record = { + VertexAIOptions: 'the new AIOptions takes a backend instead of a location, rebuild the options by hand', +}; + +export const GET_VERTEX_AI = 'getVertexAI'; +export const GET_AI = 'getAI'; +export const BACKEND_CLASS = 'VertexAIBackend'; + +// Modules whose getAI / VertexAIBackend are (or become, once rewritten) the AI Logic ones. A +// binding of those names from anywhere else must not be captured by the rewritten calls. +export const AI_MODULE_SPECIFIERS = new Set([ + ...Object.keys(MODULE_SPECIFIER_REWRITES), + ...Object.values(MODULE_SPECIFIER_REWRITES), +]); diff --git a/src/schematics/update/v21/vertexai-to-ai/types.ts b/src/schematics/update/v21/vertexai-to-ai/types.ts new file mode 100644 index 000000000..05b29da04 --- /dev/null +++ b/src/schematics/update/v21/vertexai-to-ai/types.ts @@ -0,0 +1,131 @@ +// The interfaces the rewrite pipeline passes between its stages: text edits, the pass-1 scan +// result, the getVertexAI classification, and the per-file outcome handed back for logging. +// FileContext carries the resolved TypeScript compiler with the parsed file, so no module +// holds compiler state. + +import type * as ts from 'typescript'; + +/** The resolved TypeScript compiler and the parsed file every stage operates on. */ +export interface FileContext { + compiler: typeof ts; + sourceFile: ts.SourceFile; +} + +/** A single text replacement to apply to a source file, as a `[start, end)` span and its new text. */ +export interface TextEdit { + start: number; + end: number; + replacement: string; +} + +/** A named getVertexAI import discovered in pass 1, resolved to edits only after its usages are classified. */ +export interface VertexImport { + element: ts.ImportSpecifier; + statement: ts.ImportDeclaration; + localName: string; +} + +/** One getVertexAI occurrence the rewrite intentionally leaves in place, for the migration log. */ +export interface UnsupportedVertexUsage { + position: number; + reason: string; + /** + * 'binding' usages force every named-import getVertexAI edit in the file to be skipped (a + * half-rewritten binding cannot compile). 'namespace' and 'export' usages only affect themselves. + */ + origin: 'binding' | 'namespace' | 'export'; +} + +/** A getVertexAI call whose argument shape the rewrite understands. */ +export interface SupportedVertexCall { + call: ts.CallExpression; + /** The `location` option's source text when the call passed `{ location: ... }`, else undefined. */ + locationText?: string; + /** Set for `getVertexAI(...)` through a named import, undefined for `ns.getVertexAI(...)`. */ + binding?: VertexImport; + /** Set for `ns.getVertexAI(...)`. The backend class is then reached as `ns.VertexAIBackend`. */ + namespaceAccess?: ts.PropertyAccessExpression; +} + +/** What classifying every getVertexAI reference in a file produced. */ +export interface VertexClassification { + supportedCalls: SupportedVertexCall[]; + unsupported: UnsupportedVertexUsage[]; +} + +/** An import binding excluded from the rewrite because a local declaration reuses its name. */ +export interface ShadowedImport { + position: number; + name: string; +} + +/** An imported name that no longer exists in the new entry points (see REMOVED_SYMBOL_GUIDANCE). */ +export interface RemovedSymbol { + position: number; + name: string; +} + +/** What pass 1 (declarations) discovers and hands to pass 2 (usages). */ +export interface DeclarationScan { + edits: TextEdit[]; + /** + * Un-aliased imported names whose in-file usages must also be renamed (an aliased import + * keeps its local name, so it is not tracked here). + */ + renamedLocalBindings: Map; + /** + * Namespace import names (import * as ns) whose `ns.oldSymbol` accesses must be renamed + * in both value position (PropertyAccessExpression) and type position (QualifiedName). + */ + namespaceBindings: Set; + /** Specifier name tokens already edited in pass 1. The usage pass must not touch them again. */ + editedSpecifierTokenStarts: Set; + /** Named getVertexAI imports, held back from editing until pass 2 classifies their usages. */ + vertexImports: VertexImport[]; + /** + * getVertexAI inside `export { ... } from ''`: always left in place (rewriting it + * to getAI would silently change which backend the re-export's callers reach). + */ + vertexExportSpecifiers: ts.ExportSpecifier[]; + /** + * Import bindings excluded because the file declares a local of the same name: the usage passes + * match by identifier text, so a shadowed name would get its LOCAL usages silently redirected + * to the import. Excluded bindings are left whole (loud compile break) and warned about. + */ + shadowedImports: ShadowedImport[]; + /** + * `export * from ''` statements: rewriting the specifier would silently rename the + * file's re-exported public symbols, so the statement is left whole and warned about. + */ + starExportPositions: number[]; + /** Imported names that were removed rather than renamed: left as is (loud break) and warned. */ + removedSymbols: RemovedSymbol[]; +} + +/** The getVertexAI edit set buildVertexEdits produces for one file. */ +export interface VertexEdits { + edits: TextEdit[]; + /** Offsets of getVertexAI calls rewritten to the backend-preserving getAI form, for the log. */ + callPositions: number[]; + /** Offsets of rewritable calls skipped because the file's getVertexAI edits are blocked. */ + blockedCallPositions: number[]; + /** Whether a getAI / VertexAIBackend binding from a non AI Logic source blocked the edits. */ + injectionBlocked: boolean; +} + +/** The full result of rewriting one source file. */ +export interface FileRewrite { + edits: TextEdit[]; + /** Offsets of getVertexAI calls rewritten to the backend-preserving getAI form, for the log. */ + vertexCallPositions: number[]; + /** Offsets of rewritable calls skipped because the file's getVertexAI edits are blocked. */ + blockedCallPositions: number[]; + unsupportedVertexUsages: UnsupportedVertexUsage[]; + shadowedImports: ShadowedImport[]; + /** Bindings of getAI / VertexAIBackend from a non AI Logic source that blocked the rewrite. */ + injectionConflicts: ShadowedImport[]; + /** `export * from ''` statements, always left for manual migration. */ + starExportPositions: number[]; + /** Imported names that were removed rather than renamed: left as is (loud break) and warned. */ + removedSymbols: RemovedSymbol[]; +} diff --git a/src/schematics/update/v21/vertexai-to-ai/vertex-edits.ts b/src/schematics/update/v21/vertexai-to-ai/vertex-edits.ts new file mode 100644 index 000000000..59c9e42e3 --- /dev/null +++ b/src/schematics/update/v21/vertexai-to-ai/vertex-edits.ts @@ -0,0 +1,162 @@ +// Builders for the getVertexAI edits: the backend-preserving call rewrite, the import +// specifier rename/repurpose/removal, and the VertexAIBackend import, with file-wide +// dedup so no binding is ever injected twice. + +import type * as ts from 'typescript'; +import { BACKEND_CLASS, GET_AI, GET_VERTEX_AI } from './tables.js'; +import type { DeclarationScan, FileContext, ShadowedImport, SupportedVertexCall, TextEdit, VertexClassification, VertexEdits, VertexImport } from './types.js'; + +/** + * Turn the classified getVertexAI references into edits: rewritten calls, the import specifier + * rename/replacement/removal, and the VertexAIBackend import. Every named-binding edit in the + * file is skipped, so the binding stays coherent (the old import then fails to compile loudly, + * and the log says why), when either a named-binding reference was unsupported or the file binds + * getAI / VertexAIBackend from a non AI Logic source that the injected references would hit. + * + * @returns the getVertexAI edits plus the rewritten and skipped call positions for the log. + */ +export const buildVertexEdits = ( + fileContext: FileContext, + scan: DeclarationScan, + vertex: VertexClassification, + injectionConflicts: ShadowedImport[], +): VertexEdits => { + const { sourceFile } = fileContext; + const edits: TextEdit[] = []; + const callPositions: number[] = []; + const blockedCallPositions: number[] = []; + const injectionBlocked = injectionConflicts.length > 0 && scan.vertexImports.length > 0; + const bindingBlocked = vertex.unsupported.some(usage => usage.origin === 'binding') || injectionBlocked; + + const statementsNeedingBackend = new Set(); + for (const supported of vertex.supportedCalls) { + // A blocked file skips its named-binding calls, recording each for the log. + if (supported.binding && bindingBlocked) { + blockedCallPositions.push(supported.call.getStart(sourceFile)); + continue; + } + // Namespace calls reach the backend as `ns.VertexAIBackend`, named imports as a bare name. + const backendReference = supported.namespaceAccess + ? `${supported.namespaceAccess.expression.getText(sourceFile)}.${BACKEND_CLASS}` + : BACKEND_CLASS; + // Rename an un-aliased callee to getAI. An aliased local keeps its name. + if (supported.binding) { + statementsNeedingBackend.add(supported.binding.statement); + if (supported.binding.localName === GET_VERTEX_AI) { + const callee = supported.call.expression; + edits.push({ start: callee.getStart(sourceFile), end: callee.getEnd(), replacement: GET_AI }); + } + } + // Rename `ns.getVertexAI` to `ns.getAI`. + if (supported.namespaceAccess) { + const nameNode = supported.namespaceAccess.name; + edits.push({ start: nameNode.getStart(sourceFile), end: nameNode.getEnd(), replacement: GET_AI }); + } + // Pin the call's arguments to the Vertex AI backend. + edits.push(...vertexArgumentEdits(supported, fileContext, backendReference)); + callPositions.push(supported.call.getStart(sourceFile)); + } + + if (!bindingBlocked) { + // Local-name checks are FILE-wide and the backend import is added at most once, so two + // getVertexAI specifiers (or two old-module statements) cannot inject duplicate bindings. + const importedLocals = importedLocalNames(fileContext); + let backendProvided = importedLocals.has(BACKEND_CLASS); + for (const vertexImport of scan.vertexImports) { + const addBackend = statementsNeedingBackend.has(vertexImport.statement) && !backendProvided; + edits.push(...vertexSpecifierEdits(vertexImport, addBackend, importedLocals.has(GET_AI), fileContext)); + if (addBackend) { + backendProvided = true; + } + } + } + return { edits, callPositions, blockedCallPositions, injectionBlocked }; +}; + +/** Every local name bound by any import declaration in the file (default, namespace, and named). */ +const importedLocalNames = (fileContext: FileContext): Set => { + const { compiler: tsc, sourceFile } = fileContext; + const names = new Set(); + for (const statement of sourceFile.statements) { + if (!tsc.isImportDeclaration(statement) || !statement.importClause) { + continue; + } + if (statement.importClause.name) { + names.add(statement.importClause.name.text); + } + const namedBindings = statement.importClause.namedBindings; + if (!namedBindings) { + continue; + } + if (tsc.isNamespaceImport(namedBindings)) { + names.add(namedBindings.name.text); + } else { + for (const element of namedBindings.elements) { + names.add(element.name.text); + } + } + } + return names; +}; + +/** + * Build the argument edits that pin one rewritten call to the Vertex AI backend. + */ +const vertexArgumentEdits = (supported: SupportedVertexCall, fileContext: FileContext, backendReference: string): TextEdit[] => { + const { sourceFile } = fileContext; + const backendObject = `{ backend: new ${backendReference}(${supported.locationText ?? ''}) }`; + const callArguments = supported.call.arguments; + if (callArguments.length === 0) { + // getAI's app parameter is optional but positional, so the options need an explicit undefined. + return [{ start: supported.call.getEnd() - 1, end: supported.call.getEnd() - 1, replacement: `undefined, ${backendObject}` }]; + } + if (callArguments.length === 1) { + const appArgument = callArguments[0]; + return [{ start: appArgument.getEnd(), end: appArgument.getEnd(), replacement: `, ${backendObject}` }]; + } + const optionsArgument = callArguments[1]; + return [{ start: optionsArgument.getStart(sourceFile), end: optionsArgument.getEnd(), replacement: backendObject }]; +}; + +/** + * Build the import-specifier edits for one named getVertexAI import: rename it to getAI, or, when + * the file already binds a local getAI, remove or repurpose it, and add the VertexAIBackend import + * when the caller says a rewritten call needs it (at most once per file). + */ +const vertexSpecifierEdits = (vertexImport: VertexImport, addBackend: boolean, fileHasGetAILocal: boolean, fileContext: FileContext): TextEdit[] => { + const { compiler: tsc, sourceFile } = fileContext; + const edits: TextEdit[] = []; + const namedBindings = vertexImport.statement.importClause?.namedBindings; + if (!namedBindings || !tsc.isNamedImports(namedBindings)) { + return edits; + } + const elements = namedBindings.elements; + const element = vertexImport.element; + const importedNameNode = element.propertyName ?? element.name; + + // A local getAI already exists (this statement or another import): renaming would bind getAI + // twice, so the specifier is repurposed or removed instead. + if (!element.propertyName && fileHasGetAILocal) { + // Repurpose it for the backend import. + if (addBackend) { + return [{ start: element.getStart(sourceFile), end: element.getEnd(), replacement: BACKEND_CLASS }]; + } + // The only specifier: leave an empty named import behind. + if (elements.length === 1) { + return [{ start: element.getStart(sourceFile), end: element.getEnd(), replacement: '' }]; + } + // Remove the specifier together with the comma between it and its neighbor. + const index = elements.indexOf(element); + return index > 0 + ? [{ start: elements[index - 1].getEnd(), end: element.getEnd(), replacement: '' }] + : [{ start: element.getStart(sourceFile), end: elements[1].getStart(sourceFile), replacement: '' }]; + } + + // Rename getVertexAI to getAI (on the imported name, so an alias keeps its local name), and + // append the backend import when a rewritten call needs it. + edits.push({ start: importedNameNode.getStart(sourceFile), end: importedNameNode.getEnd(), replacement: GET_AI }); + if (addBackend) { + edits.push({ start: element.getEnd(), end: element.getEnd(), replacement: `, ${BACKEND_CLASS}` }); + } + return edits; +}; diff --git a/src/schematics/update/v7/index.ts b/src/schematics/update/v7/index.ts new file mode 100644 index 000000000..a8fd5c56f --- /dev/null +++ b/src/schematics/update/v7/index.ts @@ -0,0 +1,87 @@ +import { join } from 'path'; +import { Rule, SchematicContext, SchematicsException, Tree } from '@angular-devkit/schematics'; +import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks'; +import { overwriteIfExists, safeReadJSON, stringifyFormatted } from '../../common'; +import { firebaseFunctionsDependencies, peerDependencies } from '../../versions.json'; + +const IMPORT_REGEX = /(?import|export)\s+(?:(?[\w,{}\s*]+)\s+from)?\s*(?:(?["'])?(?[@\w\s\\/.-]+)\3?)\s*(?[;\n])/g; +interface ImportRegexMatch { + key: string; + alias: string; + ref: string; + quote: string; + term: string; +} + +export const ngUpdate = (): Rule => ( + host: Tree, + context: SchematicContext +) => { + const packageJson = host.exists('package.json') && safeReadJSON('package.json', host); + + if (packageJson === undefined) { + throw new SchematicsException('Could not locate package.json'); + } + + Object.keys(peerDependencies).forEach(depName => { + const dep = peerDependencies[depName]; + if (dep) { + packageJson[dep.dev ? 'devDependencies' : 'dependencies'][depName] = dep.version; + } + }); + + // TODO test if it's a SSR project in the JSON + Object.keys(firebaseFunctionsDependencies).forEach(depName => { + const dep = firebaseFunctionsDependencies[depName]; + if (dep.dev && packageJson.devDependencies[depName]) { + packageJson.devDependencies[depName] = dep.version; + } else if (packageJson.dependencies[depName]) { + packageJson.dependencies[depName] = dep.version; + } + }); + + overwriteIfExists(host, 'package.json', stringifyFormatted(packageJson)); + context.addTask(new NodePackageInstallTask()); + + const angularJson = host.exists('angular.json') && safeReadJSON('angular.json', host); + if (packageJson === undefined) { + throw new SchematicsException('Could not locate angular.json'); + } + + // TODO investigate if this is correct in Windows + const srcRoots: string[] = Object.values(angularJson.projects).map((it: any) => + join(...['/', it.root, it.sourceRoot].filter(it => !!it)) + ); + + host.visit(filePath => { + if ( + !filePath.endsWith('.ts') || + filePath.endsWith('.d.ts') || + !srcRoots.find(root => filePath.startsWith(root)) + ) { + return; + } + const content = host.read(filePath)?.toString(); + if (!content) { + return; + } + const newContent = content.replace(IMPORT_REGEX, (substring, ...args) => { + const { alias, key, ref, quote, term }: ImportRegexMatch = args.pop(); + if (ref.startsWith('@angular/fire') && !ref.startsWith('@angular/fire/compat')) { + return `${key} ${alias} from ${quote}${ref.replace('@angular/fire', '@angular/fire/compat')}${quote}${term}`; + } + if (ref.startsWith('firebase') && !ref.startsWith('firebase/compat')) { + return `${key} ${alias} from ${quote}${ref.replace('firebase', 'firebase/compat')}${quote}${term}`; + } + if (ref.startsWith('@firebase')) { + return `${key} ${alias} from ${quote}${ref.replace('@firebase', 'firebase')}${quote}${term}`; + } + return substring; + }); + if (content !== newContent) { + overwriteIfExists(host, filePath, newContent); + } + }); + + return host; +}; diff --git a/src/schematics/utils.jasmine.ts b/src/schematics/utils.jasmine.ts new file mode 100644 index 000000000..4c7e50834 --- /dev/null +++ b/src/schematics/utils.jasmine.ts @@ -0,0 +1,123 @@ +import { ConnectorConfig } from './interfaces.js'; +import { + connectorConfigObjectLiteral, + isValidPackageSpecifier, + resolveDataConnectProviderConfig, +} from './utils.js'; +import 'jasmine'; + +// eslint-disable-next-line @typescript-eslint/no-implied-eval +const evalObjectLiteral = (literal: string) => new Function(`return (${literal});`)(); + +describe('connectorConfigObjectLiteral', () => { + + it('escapes a quote-bearing value so it round-trips as inert string data', () => { + const injectionAttempt = 'us-central1"; console.log("INJECTED"); const _z="x'; + const literal = connectorConfigObjectLiteral({ + location: injectionAttempt, + connector: 'my-connector', + service: 'my-service', + } as ConnectorConfig); + const evaluated = evalObjectLiteral(literal); + expect(evaluated.location).toBe(injectionAttempt); + }); + + it('coerces a non-string value from an unquoted yaml scalar to a string', () => { + const literal = connectorConfigObjectLiteral({ + location: 'us-central1', + connector: 'my-connector', + service: 123, + } as unknown as ConnectorConfig); + const evaluated = evalObjectLiteral(literal); + expect(evaluated.service).toBe('123'); + expect(typeof evaluated.service).toBe('string'); + }); + + it('coerces a boolean-like yaml scalar to a string', () => { + const literal = connectorConfigObjectLiteral({ + location: 'us-central1', + connector: true, + service: 'my-service', + } as unknown as ConnectorConfig); + const evaluated = evalObjectLiteral(literal); + expect(evaluated.connector).toBe('true'); + expect(typeof evaluated.connector).toBe('string'); + }); + +}); + +describe('isValidPackageSpecifier', () => { + + it('accepts a normal scoped package specifier', () => { + expect(isValidPackageSpecifier('@my-org/my-connector')).toBeTrue(); + }); + + it('rejects a value containing a double quote', () => { + expect(isValidPackageSpecifier('foo"; console.log("INJECTED"); const _z="x')).toBeFalse(); + }); + + it('rejects a value containing a backslash', () => { + expect(isValidPackageSpecifier('foo\\bar')).toBeFalse(); + }); + + it('rejects a value containing a newline', () => { + expect(isValidPackageSpecifier('foo\nbar')).toBeFalse(); + }); + +}); + +describe('resolveDataConnectProviderConfig', () => { + + it('resolves to the connectorConfig object literal when there is no package', () => { + const resolution = resolveDataConnectProviderConfig({ + connectorYaml: { connectorId: 'my-connector' }, + connectorConfig: { + location: 'us-central1', + connector: 'my-connector', + service: 'my-service', + }, + }); + expect(resolution.kind).toBe('literal'); + }); + + it('resolves to an external import when package is a valid specifier', () => { + const resolution = resolveDataConnectProviderConfig({ + connectorYaml: { connectorId: 'my-connector' }, + connectorConfig: { + location: 'us-central1', + connector: 'my-connector', + service: 'my-service', + }, + package: '@my-org/my-connector', + }); + expect(resolution).toEqual({ kind: 'external', package: '@my-org/my-connector' }); + }); + + it('falls back to the connectorConfig literal when package is not a valid specifier', () => { + const resolution = resolveDataConnectProviderConfig({ + connectorYaml: { connectorId: 'my-connector' }, + connectorConfig: { + location: 'us-central1', + connector: 'my-connector', + service: 'my-service', + }, + package: 'foo"; console.log("INJECTED")', + }); + expect(resolution.kind).toBe('literal'); + }); + + it('does not throw and falls back to an empty literal when there is no javascriptSdk config', () => { + expect(() => resolveDataConnectProviderConfig({ + connectorYaml: { connectorId: 'my-connector' }, + })).not.toThrow(); + expect(resolveDataConnectProviderConfig({ + connectorYaml: { connectorId: 'my-connector' }, + })).toEqual({ kind: 'literal', literal: '{}' }); + }); + + it('does not throw and falls back to an empty literal when config is null', () => { + expect(() => resolveDataConnectProviderConfig(null)).not.toThrow(); + expect(resolveDataConnectProviderConfig(null)).toEqual({ kind: 'literal', literal: '{}' }); + }); + +}); diff --git a/src/schematics/utils.ts b/src/schematics/utils.ts index 5ca1a61ea..94721e753 100644 --- a/src/schematics/utils.ts +++ b/src/schematics/utils.ts @@ -1,82 +1,426 @@ -import { experimental } from '@angular-devkit/core'; -import { readFileSync } from 'fs'; -import { FirebaseRc, Project } from './interfaces'; -import { join } from 'path'; -import { isUniversalApp } from './ng-add-ssr'; - -export async function listProjects() { - const firebase = require('firebase-tools'); - await firebase.login(); - return firebase.projects.list(); +import { readFileSync } from "fs"; +import { join } from "path"; +import { + Rule, + SchematicContext, + SchematicsException, + Tree, + chain, +} from "@angular-devkit/schematics"; +import { NodePackageInstallTask } from "@angular-devkit/schematics/tasks/index.js"; +import { addRootProvider } from "@schematics/angular/utility"; +import { parse } from "yaml"; +import { overwriteIfExists, safeReadJSON, stringifyFormatted } from "./common.js"; +import { + ConnectorConfig, + ConnectorYaml, + DataConnectConnectorConfig, + DataConnectYaml, + DeployOptions, + FEATURES, + FirebaseApp, + FirebaseRc, + PackageJson, + Workspace, +} from "./interfaces"; +import { SetupConfig } from "./setup"; + +export const shortAppId = (app?: FirebaseApp) => app?.appId?.split("/").pop(); + +export function getWorkspace(host: Tree): { + path: string; + workspace: Workspace; +} { + const path = "/angular.json"; + + const configBuffer = path && host.read(path); + if (!configBuffer) { + throw new SchematicsException(`Could not find angular.json`); + } + + + const { parse } = require("jsonc-parser"); + + const workspace = parse(configBuffer.toString()) as Workspace | undefined; + if (!workspace) { + throw new SchematicsException("Could not parse angular.json"); + } + + return { + path, + workspace, + }; } -// `fuzzy` passes either the original list of projects or an internal object -// which contains the project as a property. -const isProject = (elem: Project | { original: Project }): elem is Project => { - return (elem as { original: Project }).original === undefined; -}; +export const getProject = (options: DeployOptions, host: Tree) => { + const { workspace } = getWorkspace(host); + const projectName = options.project || workspace.defaultProject; -const searchProjects = (projects: Project[]) => { - return (_: any, input: string) => { - return Promise.resolve( - require('fuzzy') - .filter(input, projects, { - extract(el: Project) { - return `${el.projectId} ${el.displayName}`; - } - }) - .map((result: Project | { original: Project }) => { - let original: Project; - if (isProject(result)) { - original = result; - } else { - original = result.original; - } - return { - name: `${original.displayName} (${original.projectId})`, - title: original.displayName, - value: original.projectId - }; - }) + if (!projectName) { + throw new SchematicsException( + "No Angular project selected and no default project in the workspace" ); - }; + } + + const project = workspace.projects[projectName]; + if (!project) { + throw new SchematicsException( + "The specified Angular project is not defined in this workspace" + ); + } + + if (project.projectType !== "application") { + throw new SchematicsException( + `Deploy requires an Angular project type of "application" in angular.json` + ); + } + + return { project, projectName }; }; -export const projectPrompt = (projects: Project[]) => { - const inquirer = require('inquirer'); - inquirer.registerPrompt( - 'autocomplete', - require('inquirer-autocomplete-prompt') +export function getFirebaseProjectNameFromHost( + host: Tree, + target: string +): [string | undefined, string | undefined] { + const buffer = host.read("/.firebaserc"); + if (!buffer) { + return [undefined, undefined]; + } + const rc: FirebaseRc = JSON.parse(buffer.toString()); + return projectFromRc(rc, target); +} + +export function getFirebaseProjectNameFromFs( + root: string, + target: string +): [string | undefined, string | undefined] { + const path = join(root, ".firebaserc"); + try { + const buffer = readFileSync(path); + const rc: FirebaseRc = JSON.parse(buffer.toString()); + return projectFromRc(rc, target); + } catch (_) { + return [undefined, undefined]; + } +} + +const projectFromRc = ( + rc: FirebaseRc, + target: string +): [string | undefined, string | undefined] => { + const defaultProject = rc.projects?.default; + const project = Object.keys(rc.targets || {}).find( + (project) => !!rc.targets?.[project]?.hosting?.[target] ); - return inquirer.prompt({ - type: 'autocomplete', - name: 'firebaseProject', - source: searchProjects(projects), - message: 'Please select a project:' - }); + const site = project && rc.targets?.[project]?.hosting?.[target]?.[0]; + return [project || defaultProject, site]; }; -export const projectTypePrompt = (project: experimental.workspace.WorkspaceProject) => { - if (isUniversalApp(project)) { - return require('inquirer').prompt({ - type: 'confirm', - name: 'universalProject', - message: 'We detected an Angular Universal project. Do you want to deploy as a Firebase Function?' - }); +// TODO rewrite using typescript +export function addFixesToServer(host: Tree) { + const serverPath = `/server.ts`; + + if (!host.exists(serverPath)) { + return host; } - return Promise.resolve({ universalProject: false }); -}; -export function getFirebaseProjectName( - workspaceRoot: string, - target: string -): string | undefined { - const rc: FirebaseRc = JSON.parse( - readFileSync(join(workspaceRoot, '.firebaserc'), 'UTF-8') + const text = host.read(serverPath); + if (text === null) { + throw new SchematicsException(`File ${serverPath} does not exist.`); + } + const sourceText = text.toString("utf-8"); + const addZonePatch = !sourceText.includes( + "import 'zone.js/dist/zone-patch-rxjs';" ); - const targets = rc.targets || {}; - const projects = Object.keys(targets || {}); - return projects.find( - project => !!Object.keys(targets[project].hosting).find(t => t === target) + + if (addZonePatch) { + overwriteIfExists( + host, + serverPath, + sourceText.replace( + "import 'zone.js/dist/zone-node';", + `import 'zone.js/dist/zone-node'; +${addZonePatch ? "import 'zone.js/dist/zone-patch-rxjs';" : ""}` + ) + ); + } + + return host; +} + +// config.package and the connectorConfig values below come from the project's own +// dataconnect.yaml/connector.yaml, not from a trusted schema. isValidPackageSpecifier +// rejects the characters that would let a value break out of the double-quoted string +// literal it's interpolated into; it does not otherwise validate that the value is a +// well-formed module specifier. +const PACKAGE_SPECIFIER_PATTERN = /^[^'"\\\n\r]+$/; +export function isValidPackageSpecifier(pkg: string): boolean { + return PACKAGE_SPECIFIER_PATTERN.test(pkg); +} + +export function connectorConfigObjectLiteral(connectorConfig: ConnectorConfig): string { + return `{${(Object.keys(connectorConfig) as (keyof ConnectorConfig)[]).map( + (key) => `${key}: ${JSON.stringify(String(connectorConfig[key]))}` + ).join(',')}}`; +} + +export type DataConnectProviderConfigResolution = + | { kind: "external"; package: string } + | { kind: "literal"; literal: string }; + +export function resolveDataConnectProviderConfig( + config: DataConnectConnectorConfig | null | undefined +): DataConnectProviderConfigResolution { + if (config?.package && isValidPackageSpecifier(config.package)) { + return { kind: "external", package: config.package }; + } + if (config?.connectorConfig) { + return { kind: "literal", literal: connectorConfigObjectLiteral(config.connectorConfig) }; + } + return { kind: "literal", literal: "{}" }; +} + +export function featureToRules( + features: FEATURES[], + projectName: string, + dataConnectConfig?: DataConnectConnectorConfig | null +) { + return features + .map((feature) => { + switch (feature) { + case FEATURES.AppCheck: + // TODO make this smarter in Angular Universal + return addRootProvider(projectName, ({ code, external }) => { + external("initializeAppCheck", "@angular/fire/app-check"); + external("ReCaptchaEnterpriseProvider", "@angular/fire/app-check"); + return code`${external( + "provideAppCheck", + "@angular/fire/app-check" + )}(() => { + // TODO get a reCAPTCHA Enterprise here https://console.cloud.google.com/security/recaptcha?project=_ + const provider = new ReCaptchaEnterpriseProvider(/* reCAPTCHA Enterprise site key */); + return initializeAppCheck(undefined, { provider, isTokenAutoRefreshEnabled: true }); +})`; + }); + case FEATURES.Analytics: + return chain([ + addRootProvider(projectName, ({ code, external }) => { + external("getAnalytics", "@angular/fire/analytics"); + return code`${external( + "provideAnalytics", + "@angular/fire/analytics" + )}(() => getAnalytics())`; + }), + // TODO if using Angular router + addRootProvider(projectName, ({ code, external }) => { + return code`${external( + "ScreenTrackingService", + "@angular/fire/analytics" + )}`; + }), + ...(features.includes(FEATURES.Authentication) + ? [ + addRootProvider(projectName, ({ code, external }) => { + return code`${external( + "UserTrackingService", + "@angular/fire/analytics" + )}`; + }), + ] + : []), + ]); + case FEATURES.Authentication: + return addRootProvider(projectName, ({ code, external }) => { + external("getAuth", "@angular/fire/auth"); + return code`${external( + "provideAuth", + "@angular/fire/auth" + )}(() => getAuth())`; + }); + case FEATURES.Database: + return addRootProvider(projectName, ({ code, external }) => { + external("getDatabase", "@angular/fire/database"); + return code`${external( + "provideDatabase", + "@angular/fire/database" + )}(() => getDatabase())`; + }); + case FEATURES.DataConnect: + return addRootProvider(projectName, ({ code, external }) => { + external("getDataConnect", "@angular/fire/data-connect"); + + const resolution = resolveDataConnectProviderConfig(dataConnectConfig); + const configAsStr = resolution.kind === "external" + ? external("connectorConfig", resolution.package) + : resolution.literal; + + let angularConfig: undefined | string; + if (dataConnectConfig?.angular) { + angularConfig = `, ${external( + "provideTanStackQuery", + "@tanstack/angular-query-experimental" + )}(new ${external( + "QueryClient", + "@tanstack/angular-query-experimental" + )}())`; + } + return code`${external( + "provideDataConnect", + "@angular/fire/data-connect" + )}(() => getDataConnect(${configAsStr}))${angularConfig ?? ""}`; + }); + case FEATURES.Firestore: + return addRootProvider(projectName, ({ code, external }) => { + external("getFirestore", "@angular/fire/firestore"); + return code`${external( + "provideFirestore", + "@angular/fire/firestore" + )}(() => getFirestore())`; + }); + case FEATURES.Functions: + return addRootProvider(projectName, ({ code, external }) => { + external("getFunctions", "@angular/fire/functions"); + return code`${external( + "provideFunctions", + "@angular/fire/functions" + )}(() => getFunctions())`; + }); + case FEATURES.Messaging: + // TODO add the service worker + return addRootProvider(projectName, ({ code, external }) => { + external("getMessaging", "@angular/fire/messaging"); + return code`${external( + "provideMessaging", + "@angular/fire/messaging" + )}(() => getMessaging())`; + }); + case FEATURES.Performance: + return addRootProvider(projectName, ({ code, external }) => { + external("getPerformance", "@angular/fire/performance"); + return code`${external( + "providePerformance", + "@angular/fire/performance" + )}(() => getPerformance())`; + }); + case FEATURES.Storage: + return addRootProvider(projectName, ({ code, external }) => { + external("getStorage", "@angular/fire/storage"); + return code`${external( + "provideStorage", + "@angular/fire/storage" + )}(() => getStorage())`; + }); + case FEATURES.RemoteConfig: + // TODO consider downloading the defaults + return addRootProvider(projectName, ({ code, external }) => { + external("getRemoteConfig", "@angular/fire/remote-config"); + return code`${external( + "provideRemoteConfig", + "@angular/fire/remote-config" + )}(() => getRemoteConfig())`; + }); + case FEATURES.AI: + return addRootProvider(projectName, ({ code, external }) => { + external("getAI", "@angular/fire/ai"); + return code`${external( + "provideAI", + "@angular/fire/ai" + )}(() => getAI())`; + }); + default: + return undefined; + } + }) + .filter((it): it is Rule => !!it); +} + +export const addIgnoreFiles = (host: Tree) => { + const path = "/.gitignore"; + if (!host.exists(path)) { + return host; + } + + const buffer = host.read(path); + if (!buffer) { + return host; + } + + const content = buffer.toString(); + if (!content.includes("# Firebase")) { + overwriteIfExists( + host, + path, + content.concat(` +# Firebase +.firebase +*-debug.log +.runtimeconfig.json +`) + ); + } + + return host; +}; + +export function parseDataConnectConfig( + config: SetupConfig +): DataConnectConnectorConfig | null { + if (!config.firebaseJsonConfig) { + throw new Error("No firebase json"); + } + if (!config.firebaseJsonConfig.dataconnect?.source) { + throw new Error( + "Couldn't find data connect configuration. Running `firebase init dataconnect`" + ); + } + const dataConnectFolder = join( + config.firebaseJsonPath, + config.firebaseJsonConfig.dataconnect?.source ); + const sourcePath = join(dataConnectFolder, "dataconnect.yaml"); + try { + const fileAsStr = readFileSync(sourcePath).toString(); + const dataConnectYaml: DataConnectYaml = parse(fileAsStr); + const connectorPath = join( + dataConnectFolder, + dataConnectYaml.connectorDirs[0], + "connector.yaml" + ); + const connectorAsStr = readFileSync(connectorPath).toString(); + const connectorJson: ConnectorYaml = parse(connectorAsStr); + if (!connectorJson?.generate?.javascriptSdk) { + return { connectorYaml: connectorJson }; + } + return { + connectorYaml: connectorJson, + connectorConfig: { + connector: connectorJson.connectorId, + location: dataConnectYaml.location, + service: dataConnectYaml.serviceId, + }, + package: connectorJson.generate.javascriptSdk.package, + angular: connectorJson.generate.javascriptSdk.angular, + }; + } catch (e) { + console.error("Couldn't parse dataconnect.yaml", e); + return null; + } +} + +export function setupTanstackDependencies( + host: Tree, + context: SchematicContext +) { + const packageJson: PackageJson = safeReadJSON("package.json", host); + const tanstackFirebasePackage = "@tanstack-query-firebase/angular"; + if ( + !packageJson.dependencies[tanstackFirebasePackage] && + !packageJson.devDependencies[tanstackFirebasePackage] + ) { + packageJson.dependencies[tanstackFirebasePackage] = + "^1.0.0"; + packageJson.dependencies["@tanstack/angular-query-experimental"] = "5.66.4"; + overwriteIfExists(host, "package.json", stringifyFormatted(packageJson)); + context.addTask(new NodePackageInstallTask()); + } } diff --git a/src/schematics/versions.json b/src/schematics/versions.json index fb87ebe05..9477d49a4 100644 --- a/src/schematics/versions.json +++ b/src/schematics/versions.json @@ -1,16 +1,8 @@ { - "default": { - "firebase": { "version": "0.0.0" }, - "@angular-devkit/architect": { "dev": true, "version": "0.0.0" }, - "firebase-tools": { "dev": true, "version": "0.0.0" }, - "fuzzy": { "dev": true, "version": "0.0.0"}, - "inquirer": { "dev": true, "version": "0.0.0"}, - "inquirer-autocomplete-prompt": { "dev": true, "version": "0.0.0"}, - "open": { "dev": true, "version": "0.0.0"} + "peerDependencies": { }, - "firebaseFunctions": { - "firebase-admin": { "dev": true, "version": "0.0.0" }, - "firebase-functions": { "dev": true, "version": "0.0.0" }, - "firebase-functions-test": { "dev": true, "version": "0.0.0" } + "firebaseFunctionsDependencies": { + "firebase-admin": { "dev": false, "version": "0.0.0" }, + "firebase-functions": { "dev": false, "version": "0.0.0" } } } diff --git a/src/storage/firebase.ts b/src/storage/firebase.ts new file mode 100644 index 000000000..3ad53a491 --- /dev/null +++ b/src/storage/firebase.ts @@ -0,0 +1,36 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +export * from 'firebase/storage'; +import { ÉĩzoneWrap } from '@angular/fire'; +import { + connectStorageEmulator as _connectStorageEmulator, + deleteObject as _deleteObject, + getBlob as _getBlob, + getBytes as _getBytes, + getDownloadURL as _getDownloadURL, + getMetadata as _getMetadata, + getStorage as _getStorage, + getStream as _getStream, + list as _list, + listAll as _listAll, + ref as _ref, + updateMetadata as _updateMetadata, + uploadBytes as _uploadBytes, + uploadBytesResumable as _uploadBytesResumable, + uploadString as _uploadString +} from 'firebase/storage'; + +export const connectStorageEmulator = ÉĩzoneWrap(_connectStorageEmulator, true); +export const deleteObject = ÉĩzoneWrap(_deleteObject, true, 2); +export const getBlob = ÉĩzoneWrap(_getBlob, true); +export const getBytes = ÉĩzoneWrap(_getBytes, true); +export const getDownloadURL = ÉĩzoneWrap(_getDownloadURL, true); +export const getMetadata = ÉĩzoneWrap(_getMetadata, true); +export const getStorage = ÉĩzoneWrap(_getStorage, true); +export const getStream = ÉĩzoneWrap(_getStream, true); +export const list = ÉĩzoneWrap(_list, true); +export const listAll = ÉĩzoneWrap(_listAll, true); +export const ref = ÉĩzoneWrap(_ref, true, 2); +export const updateMetadata = ÉĩzoneWrap(_updateMetadata, true, 2); +export const uploadBytes = ÉĩzoneWrap(_uploadBytes, true); +export const uploadBytesResumable = ÉĩzoneWrap(_uploadBytesResumable, true); +export const uploadString = ÉĩzoneWrap(_uploadString, true); diff --git a/src/storage/interfaces.ts b/src/storage/interfaces.ts deleted file mode 100644 index 9ad9b10fa..000000000 --- a/src/storage/interfaces.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { storage } from 'firebase/app'; - -export type UploadTask = storage.UploadTask; -export type UploadTaskSnapshot = storage.UploadTaskSnapshot; -export type UploadMetadata = storage.UploadMetadata; - -export type SettableMetadata = storage.SettableMetadata; -export type Reference = storage.Reference; -export type StringFormat = storage.StringFormat; -export type ListResult = storage.ListResult; diff --git a/src/storage/ng-package.json b/src/storage/ng-package.json new file mode 100644 index 000000000..d3032da35 --- /dev/null +++ b/src/storage/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/storage/observable/fromTask.ts b/src/storage/observable/fromTask.ts deleted file mode 100644 index 8b485eae1..000000000 --- a/src/storage/observable/fromTask.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Observable } from 'rxjs'; -import { UploadTask, UploadTaskSnapshot } from '../interfaces'; - -export function fromTask(task: UploadTask) { - return new Observable(subscriber => { - const progress = (snap: UploadTaskSnapshot) => subscriber.next(snap); - const error = e => subscriber.error(e); - const complete = () => subscriber.complete(); - task.on('state_changed', progress, error, () => { - progress(task.snapshot); - complete(); - }); - return () => task.cancel(); - }); -} diff --git a/src/storage/package.json b/src/storage/package.json index 252f86ad2..8dce418a5 100644 --- a/src/storage/package.json +++ b/src/storage/package.json @@ -1,12 +1,3 @@ { - "$schema": "../../node_modules/ng-packagr/package.schema.json", - "ngPackage": { - "lib": { - "entryFile": "public_api.ts", - "umdModuleIds": { - "firebase/app": "firebase", - "@firebase/storage": "firebase-storage" - } - } - } + "$schema": "../../node_modules/ng-packagr/package.schema.json" } diff --git a/src/storage/public_api.ts b/src/storage/public_api.ts index a2848a409..e75f18503 100644 --- a/src/storage/public_api.ts +++ b/src/storage/public_api.ts @@ -1,5 +1,4 @@ -export * from './ref'; -export * from './storage'; -export * from './task'; -export * from './observable/fromTask'; -export * from './storage.module'; +export { Storage, StorageInstances, storageInstance$ } from './storage'; +export { provideStorage, StorageModule } from './storage.module'; +export * from './rxfire'; +export * from './firebase'; diff --git a/src/storage/rxfire.ts b/src/storage/rxfire.ts new file mode 100644 index 000000000..a4ef73dd4 --- /dev/null +++ b/src/storage/rxfire.ts @@ -0,0 +1,9 @@ +// DO NOT MODIFY, this file is autogenerated by tools/build.ts +import { ÉĩzoneWrap } from '@angular/fire'; +import { + fromTask as _fromTask, + percentage as _percentage +} from 'rxfire/storage'; + +export const fromTask = ÉĩzoneWrap(_fromTask, true); +export const percentage = ÉĩzoneWrap(_percentage, true); diff --git a/src/storage/storage.module.ts b/src/storage/storage.module.ts index 4169db29f..0d15e2aca 100644 --- a/src/storage/storage.module.ts +++ b/src/storage/storage.module.ts @@ -1,7 +1,82 @@ -import { NgModule } from '@angular/core'; -import { AngularFireStorage } from './storage'; +import { + EnvironmentProviders, + InjectionToken, + Injector, + NgModule, + NgZone, + Optional, + makeEnvironmentProviders, +} from '@angular/core'; +import { VERSION, ÉĩAngularFireSchedulers, ÉĩgetDefaultInstanceOf } from '@angular/fire'; +import { FirebaseApp, FirebaseApps } from '@angular/fire/app'; +import { AppCheckInstances } from '@angular/fire/app-check'; +import { AuthInstances } from '@angular/fire/auth'; +import { registerVersion } from 'firebase/app'; +import { FirebaseStorage } from 'firebase/storage'; +import { STORAGE_PROVIDER_NAME, Storage, StorageInstances } from './storage'; + +export const PROVIDED_STORAGE_INSTANCES = new InjectionToken('angularfire2.storage-instances'); + +export function defaultStorageInstanceFactory(provided: FirebaseStorage[]|undefined, defaultApp: FirebaseApp) { + const defaultStorage = ÉĩgetDefaultInstanceOf(STORAGE_PROVIDER_NAME, provided, defaultApp); + return defaultStorage && new Storage(defaultStorage); +} + +export function storageInstanceFactory(fn: (injector: Injector) => FirebaseStorage) { + return (zone: NgZone, injector: Injector) => { + const storage = zone.runOutsideAngular(() => fn(injector)); + return new Storage(storage); + }; +} + +const STORAGE_INSTANCES_PROVIDER = { + provide: StorageInstances, + deps: [ + [new Optional(), PROVIDED_STORAGE_INSTANCES ], + ] +}; + +const DEFAULT_STORAGE_INSTANCE_PROVIDER = { + provide: Storage, + useFactory: defaultStorageInstanceFactory, + deps: [ + [new Optional(), PROVIDED_STORAGE_INSTANCES ], + FirebaseApp, + ] +}; @NgModule({ - providers: [ AngularFireStorage ] + providers: [ + DEFAULT_STORAGE_INSTANCE_PROVIDER, + STORAGE_INSTANCES_PROVIDER, + ] }) -export class AngularFireStorageModule { } +export class StorageModule { + constructor() { + registerVersion('angularfire', VERSION.full, 'gcs'); + } +} + +export function provideStorage(fn: (injector: Injector) => FirebaseStorage, ...deps: any[]): EnvironmentProviders { + registerVersion('angularfire', VERSION.full, 'gcs'); + + return makeEnvironmentProviders([ + DEFAULT_STORAGE_INSTANCE_PROVIDER, + STORAGE_INSTANCES_PROVIDER, + { + provide: PROVIDED_STORAGE_INSTANCES, + useFactory: storageInstanceFactory(fn), + multi: true, + deps: [ + NgZone, + Injector, + ÉĩAngularFireSchedulers, + FirebaseApps, + // Defensively load Auth first, if provided + [new Optional(), AuthInstances ], + [new Optional(), AppCheckInstances ], + ...deps, + ] + } + ]); +} diff --git a/src/storage/storage.spec.ts b/src/storage/storage.spec.ts index 083d4b645..b60331e8a 100644 --- a/src/storage/storage.spec.ts +++ b/src/storage/storage.spec.ts @@ -1,217 +1,39 @@ -import { forkJoin } from 'rxjs'; -import { mergeMap, tap } from 'rxjs/operators'; import { TestBed } from '@angular/core/testing'; -import { AngularFireModule, FIREBASE_APP_NAME, FIREBASE_OPTIONS, FirebaseApp } from '@angular/fire'; -import { AngularFireStorage, AngularFireStorageModule, AngularFireUploadTask, BUCKET } from './public_api'; +import { FirebaseApp, getApp, initializeApp, provideFirebaseApp } from '@angular/fire/app'; +import { Storage, connectStorageEmulator, getStorage, provideStorage } from '@angular/fire/storage'; import { COMMON_CONFIG } from '../test-config'; -import 'firebase/storage'; -import { rando } from '../firestore/utils.spec'; +import { rando } from '../utils'; -describe('AngularFireStorage', () => { +describe('Storage', () => { let app: FirebaseApp; - let afStorage: AngularFireStorage; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - AngularFireModule.initializeApp(COMMON_CONFIG, rando()), - AngularFireStorageModule - ] - }); - - app = TestBed.inject(FirebaseApp); - afStorage = TestBed.inject(AngularFireStorage); - }); - - afterEach(() => { - app.delete(); - }); - - it('should exist', () => { - expect(afStorage instanceof AngularFireStorage).toBe(true); - }); - - it('should have the Firebase storage instance', () => { - expect(afStorage.storage).toBeDefined(); - }); - - it('should have an initialized Firebase app', () => { - expect(afStorage.storage.app).toBeDefined(); - }); - - // TODO tests for node? - if (typeof Blob !== 'undefined') { - - describe('upload task', () => { - - it('should upload and delete a file', (done) => { - const data = { angular: 'fire' }; - const blob = new Blob([JSON.stringify(data)], { type: 'application/json' }); - const ref = afStorage.ref('af.json'); - const task = ref.put(blob); - task.snapshotChanges() - .subscribe( - snap => { - expect(snap).toBeDefined(); - }, - done.fail, - () => { - ref.delete().subscribe(done, done.fail); - }); - }); - - it('should upload a file and observe the download url', (done) => { - const data = { angular: 'fire' }; - const blob = new Blob([JSON.stringify(data)], { type: 'application/json' }); - const ref = afStorage.ref('af.json'); - ref.put(blob).then(() => { - const url$ = ref.getDownloadURL(); - url$.subscribe( - url => { - expect(url).toBeDefined(); - }, - done.fail, - () => { - ref.delete().subscribe(done, done.fail); - } - ); + let storage: Storage; + let providedStorage: Storage; + let appName: string; + + describe('single injection', () => { + + beforeEach(() => { + appName = rando(); + TestBed.configureTestingModule({ + providers: [ + provideFirebaseApp(() => initializeApp(COMMON_CONFIG, appName)), + provideStorage(() => { + providedStorage = getStorage(getApp(appName)); + connectStorageEmulator(providedStorage, 'localhost', 9199); + return providedStorage; + }), + ], }); - }); - - it('should resolve the task as a promise', (done) => { - const data = { angular: 'promise' }; - const blob = new Blob([JSON.stringify(data)], { type: 'application/json' }); - const ref = afStorage.ref('af.json'); - const task: AngularFireUploadTask = ref.put(blob); - task.then(snap => { - expect(snap).toBeDefined(); - done(); - }).catch(done.fail); - }); - - }); - - describe('reference', () => { - - it('it should upload, download, and delete', (done) => { - const data = { angular: 'fire' }; - const blob = new Blob([JSON.stringify(data)], { type: 'application/json' }); - const ref = afStorage.ref('af.json'); - const task = ref.put(blob); - // Wait for the upload - forkJoin([task.snapshotChanges()]) - .pipe( - // get the url download - mergeMap(() => ref.getDownloadURL()), - // assert the URL - tap(url => expect(url).toBeDefined()), - // Delete the file - mergeMap(() => ref.delete()) - ) - // finish the test - .subscribe(done, done.fail); - }); - - it('should upload, get metadata, and delete', (done) => { - const data = { angular: 'fire' }; - const blob = new Blob([JSON.stringify(data)], { type: 'application/json' }); - const ref = afStorage.ref('af.json'); - const task = ref.put(blob, { customMetadata: { blah: 'blah' } }); - // Wait for the upload - forkJoin([task.snapshotChanges()]) - .pipe( - // get the metadata download - mergeMap(() => ref.getMetadata()), - // assert the URL - tap(meta => expect(meta.customMetadata).toEqual({ blah: 'blah' })), - // Delete the file - mergeMap(() => ref.delete()) - ) - // finish the test - .subscribe(done, done.fail); - }); - + app = TestBed.inject(FirebaseApp); + storage = TestBed.inject(Storage); }); - } - -}); - -describe('AngularFireStorage w/options', () => { - let app: FirebaseApp; - let afStorage: AngularFireStorage; - let firebaseAppName: string; - let storageBucket: string; - - beforeEach(() => { - firebaseAppName = rando(); - storageBucket = 'angularfire2-test2'; - TestBed.configureTestingModule({ - imports: [ - AngularFireModule.initializeApp(COMMON_CONFIG, rando()), - AngularFireStorageModule - ], - providers: [ - { provide: FIREBASE_APP_NAME, useValue: firebaseAppName }, - { provide: FIREBASE_OPTIONS, useValue: COMMON_CONFIG }, - { provide: BUCKET, useValue: storageBucket } - ] - }); - - app = TestBed.inject(FirebaseApp); - afStorage = TestBed.inject(AngularFireStorage); - }); - - afterEach(() => { - app.delete(); - }); - - describe('', () => { - - it('should exist', () => { - expect(afStorage instanceof AngularFireStorage).toBe(true); + it('should be injectable', () => { + expect(providedStorage).toBeTruthy(); + expect(storage).toEqual(providedStorage); + expect(storage.app).toEqual(app); }); - it('should have the Firebase storage instance', () => { - expect(afStorage.storage).toBeDefined(); - }); - - it('should have an initialized Firebase app', () => { - expect(afStorage.storage.app).toBeDefined(); - }); - - it('should be hooked up the right app', () => { - expect(afStorage.storage.app.name).toEqual(firebaseAppName); - }); - - it('storage be pointing towards a different bucket', () => { - expect(afStorage.storage.ref().toString()).toEqual(`gs://${storageBucket}/`); - }); - - // TODO tests for Node? - if (typeof Blob !== 'undefined') { - - it('it should upload, download, and delete', (done) => { - const data = { angular: 'fire' }; - const blob = new Blob([JSON.stringify(data)], { type: 'application/json' }); - const ref = afStorage.ref('af.json'); - const task = ref.put(blob); - // Wait for the upload - forkJoin([task.snapshotChanges()]) - .pipe( - // get the url download - mergeMap(() => ref.getDownloadURL()), - // assert the URL - tap(url => expect(url).toMatch(new RegExp(`https:\\/\\/firebasestorage\\.googleapis\\.com\\/v0\\/b\\/${storageBucket}\\/o\\/af\\.json`))), - // Delete the file - mergeMap(() => ref.delete()) - ) - // finish the test - .subscribe(done, done.fail); - }); - - } - }); }); diff --git a/src/storage/storage.ts b/src/storage/storage.ts index 0ef5f3fbf..e2e47e8d9 100644 --- a/src/storage/storage.ts +++ b/src/storage/storage.ts @@ -1,67 +1,30 @@ -import { Inject, Injectable, InjectionToken, NgZone, Optional, PLATFORM_ID } from '@angular/core'; -import { createStorageRef } from './ref'; -import { Observable } from 'rxjs'; -import { - FIREBASE_APP_NAME, - FIREBASE_OPTIONS, - FirebaseAppConfig, - FirebaseOptions, - ÉĩAngularFireSchedulers, - ÉĩfirebaseAppFactory, - ÉĩkeepUnstableUntilFirstFactory -} from '@angular/fire'; -import { UploadMetadata } from './interfaces'; -import { storage } from 'firebase/app'; -import 'firebase/storage'; -import firebase from 'firebase/app'; -import { registerStorage } from '@firebase/storage'; - -export const BUCKET = new InjectionToken('angularfire2.storageBucket'); - -/** - * AngularFireStorage Service - * - * This service is the main entry point for this feature module. It provides - * an API for uploading and downloading binary files from Cloud Storage for - * Firebase. - */ -@Injectable({ - providedIn: 'any' -}) -export class AngularFireStorage { - public readonly storage: storage.Storage; - - public readonly keepUnstableUntilFirst: (obs: Observable) => Observable; - public readonly schedulers: ÉĩAngularFireSchedulers; - - constructor( - @Inject(FIREBASE_OPTIONS) options: FirebaseOptions, - @Optional() @Inject(FIREBASE_APP_NAME) nameOrConfig: string | FirebaseAppConfig | null | undefined, - @Optional() @Inject(BUCKET) storageBucket: string | null, - // tslint:disable-next-line:ban-types - @Inject(PLATFORM_ID) platformId: Object, - zone: NgZone - ) { - this.schedulers = new ÉĩAngularFireSchedulers(zone); - this.keepUnstableUntilFirst = ÉĩkeepUnstableUntilFirstFactory(this.schedulers); - - this.storage = zone.runOutsideAngular(() => { - const app = ÉĩfirebaseAppFactory(options, zone, nameOrConfig); - if (registerStorage) { - registerStorage(firebase as any); - } - return app.storage(storageBucket || undefined); - }); +import { ÉĩgetAllInstancesOf } from '@angular/fire'; +import { FirebaseStorage } from 'firebase/storage'; +import { from, timer } from 'rxjs'; +import { concatMap, distinct } from 'rxjs/operators'; + +// see notes in core/firebase.app.module.ts for why we're building the class like this + +export interface Storage extends FirebaseStorage {} + +export class Storage { + constructor(auth: FirebaseStorage) { + return auth; } +} - ref(path: string) { - return createStorageRef(this.storage.ref(path), this.schedulers, this.keepUnstableUntilFirst); - } +export const STORAGE_PROVIDER_NAME = 'storage'; - upload(path: string, data: any, metadata?: UploadMetadata) { - const storageRef = this.storage.ref(path); - const ref = createStorageRef(storageRef, this.schedulers, this.keepUnstableUntilFirst); - return ref.put(data, metadata); - } + +export interface StorageInstances extends Array {} +export class StorageInstances { + constructor() { + return ÉĩgetAllInstancesOf(STORAGE_PROVIDER_NAME); + } } + +export const storageInstance$ = timer(0, 300).pipe( + concatMap(() => from(ÉĩgetAllInstancesOf(STORAGE_PROVIDER_NAME))), + distinct(), +); diff --git a/src/test-config.ts b/src/test-config.ts index 4f1282c10..cbebd784e 100644 --- a/src/test-config.ts +++ b/src/test-config.ts @@ -9,3 +9,31 @@ export const COMMON_CONFIG = { databaseName: 'angularfire2-test', measurementId: 'G-W20QDV5CZP' }; + +export const COMMON_CONFIG_TOO = { + apiKey: 'AIzaSyBVSy3YpkVGiKXbbxeK0qBnu3-MNZ9UIjA', + authDomain: 'angularfire2-test.firebaseapp.com', + databaseURL: 'https://angularfire2-test.firebaseio.com', + projectId: 'angularfire2-test', + storageBucket: 'angularfire2-test.appspot.com', + messagingSenderId: '920323787688', + appId: '1:920323787688:web:2253a0e5eb5b9a8d', + databaseName: 'angularfire2-test', + measurementId: 'G-W20QDV5CZZ' +}; + +declare global { + interface Window { + __karma__ : { + config: { + args: any[] + } + }; + } +} + +export const firestoreEmulatorPort: number = window.__karma__.config.args.find((it) => it[0] === "FIRESTORE_EMULATOR_PORT")[1]; +export const storageEmulatorPort: number = window.__karma__.config.args.find((it) => it[0] === "STORAGE_EMULATOR_PORT")[1]; +export const authEmulatorPort: number = window.__karma__.config.args.find((it) => it[0] === "AUTH_EMULATOR_PORT")[1]; +export const databaseEmulatorPort: number = window.__karma__.config.args.find((it) => it[0] === "DATABASE_EMULATOR_PORT")[1]; +export const functionsEmulatorPort: number = window.__karma__.config.args.find((it) => it[0] === "FUNCTIONS_EMULATOR_PORT")[1]; diff --git a/src/test.ts b/src/test.ts index d967f7532..e1b0e6633 100644 --- a/src/test.ts +++ b/src/test.ts @@ -15,7 +15,7 @@ getTestBed().initTestEnvironment( platformBrowserDynamicTesting() ); // Then we find all the tests. -const context = require.context('./', true, /\.spec\.ts$/); +const context = require.context('./', true, /^\.\/.+\.spec\.ts$/); // And load the modules. context.keys().map(context); diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 000000000..5d4aee26a --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,3 @@ +const randomString = () => (Math.random() + 1).toString(36).split('.')[1]; + +export const rando = () => [randomString(), randomString(), randomString()].join(''); diff --git a/src/zones.ts b/src/zones.ts new file mode 100644 index 000000000..62e97d2c7 --- /dev/null +++ b/src/zones.ts @@ -0,0 +1,193 @@ + +import { + EnvironmentInjector, + Injectable, + NgZone, + PendingTasks, + inject, + isDevMode, + runInInjectionContext +} from '@angular/core'; +import { pendingUntilEvent } from '@angular/core/rxjs-interop'; +import { + Observable, + SchedulerAction, + SchedulerLike, + Subscription, + asyncScheduler, + queueScheduler +} from 'rxjs'; +import { observeOn, subscribeOn } from 'rxjs/operators'; + +declare const Zone: {current: unknown} | undefined; + +export enum LogLevel { + "SILENT" = 0, + "WARN" = 1, + "VERBOSE" = 2, +} + + +var currentLogLevel = (isDevMode() && typeof Zone !== "undefined") ? LogLevel.WARN : LogLevel.SILENT; + +export const setLogLevel = (logLevel: LogLevel) => currentLogLevel = logLevel; + +/** + * Schedules tasks so that they are invoked inside the Zone that is passed in the constructor. + */ +export class ÉĩZoneScheduler implements SchedulerLike { + constructor(private zone: any, private delegate: any = queueScheduler) { + } + + now() { + return this.delegate.now(); + } + + schedule(work: (this: SchedulerAction, state?: any) => void, delay?: number, state?: any): Subscription { + const targetZone = this.zone; + // Wrap the specified work function to make sure that if nested scheduling takes place the + // work is executed in the correct zone + const workInZone = function(this: SchedulerAction, state: any) { + if (targetZone) { + targetZone.runGuarded(() => { + work.apply(this, [state]); + }); + } else { + work.apply(this, [state]); + } + }; + + // Scheduling itself needs to be run in zone to ensure setInterval calls for async scheduling are done + // inside the correct zone. This scheduler needs to schedule asynchronously always to ensure that + // firebase emissions are never synchronous. Specifying a delay causes issues with the queueScheduler delegate. + return this.delegate.schedule(workInZone, delay, state); + } +} + +@Injectable({ + providedIn: 'root', +}) +export class ÉĩAngularFireSchedulers { + public readonly outsideAngular: ÉĩZoneScheduler; + public readonly insideAngular: ÉĩZoneScheduler; + + constructor() { + const ngZone = inject(NgZone); + this.outsideAngular = ngZone.runOutsideAngular( + () => new ÉĩZoneScheduler(typeof Zone === 'undefined' ? undefined : Zone.current) + ); + this.insideAngular = ngZone.run( + () => new ÉĩZoneScheduler( + typeof Zone === 'undefined' ? undefined : Zone.current, + asyncScheduler + ) + ); + } +} + +var alreadyWarned = false; +function warnOutsideInjectionContext(original: any, logLevel: LogLevel) { + if (!alreadyWarned && (currentLogLevel > LogLevel.SILENT || isDevMode())) { + alreadyWarned = true; + console.warn("Calling Firebase APIs outside of an Injection context may destabilize your application leading to subtle change-detection and hydration bugs. Find more at https://github.com/angular/angularfire/blob/main/docs/zones.md"); + } + if (currentLogLevel >= logLevel) { + console.warn(`Firebase API called outside injection context: ${original.name}`); + } +} + +function runOutsideAngular(fn: (...args: any[]) => T): T { + const ngZone = inject(NgZone, { optional: true }); + if (!ngZone) {return fn();} + return ngZone.runOutsideAngular(() => fn()); +} + +function run(fn: (...args: any[]) => T): T { + const ngZone = inject(NgZone, { optional: true }); + if (!ngZone) {return fn();} + return ngZone.run(() => fn()); +} + +const zoneWrapFn = ( + it: (...args: any[]) => any, + taskDone: VoidFunction | undefined, + injector: EnvironmentInjector, +) => { + return (...args: any[]) => { + if (taskDone) { + setTimeout(taskDone, 0); + } + return runInInjectionContext(injector, () => run(() => it.apply(this, args))); + }; +}; + +export const ÉĩzoneWrap = (it: T, blockUntilFirst: boolean, logLevel?: LogLevel): T => { + logLevel ||= blockUntilFirst ? LogLevel.WARN : LogLevel.VERBOSE; + // function() is needed for the arguments object + return function () { + let taskDone: VoidFunction | undefined; + const _arguments = arguments; + let schedulers: ÉĩAngularFireSchedulers; + let pendingTasks: PendingTasks; + let injector: EnvironmentInjector; + try { + schedulers = inject(ÉĩAngularFireSchedulers); + pendingTasks = inject(PendingTasks); + injector = inject(EnvironmentInjector); + } catch (_) { + warnOutsideInjectionContext(it, logLevel); + return (it as any).apply(this, _arguments); + } + // if this is a callback function, e.g, onSnapshot, we should create a pending task and complete it + // only once one of the callback functions is tripped. + for (let i = 0; i < arguments.length; i++) { + if (typeof _arguments[i] === 'function') { + if (blockUntilFirst) { + taskDone ||= run(() => pendingTasks.add()); + } + // TODO create a microtask to track callback functions + _arguments[i] = zoneWrapFn(_arguments[i], taskDone, injector); + } + } + const ret = runOutsideAngular(() => (it as any).apply(this, _arguments)); + if (!blockUntilFirst) { + if (ret instanceof Observable) { + return ret.pipe( + subscribeOn(schedulers.outsideAngular), + observeOn(schedulers.insideAngular), + ); + } else { + return run(() => ret); + } + } + if (ret instanceof Observable) { + return ret.pipe( + subscribeOn(schedulers.outsideAngular), + observeOn(schedulers.insideAngular), + pendingUntilEvent(injector), + ); + } else if (ret instanceof Promise) { + + return run( + () => { + const removeTask = pendingTasks.add(); + return new Promise((resolve, reject) => { + ret.then( + (it) => runInInjectionContext(injector, () => run(() => resolve(it))), + (reason) => runInInjectionContext(injector, () => run(() => reject(reason))) + ).finally(removeTask); + }); + }); + } else if (typeof ret === 'function' && taskDone) { + // Handle unsubscribe + // function() is needed for the arguments object + return function () { + setTimeout(taskDone, 0); + return ret.apply(this, arguments); + }; + } else { + // TODO how do we handle storage uploads in Zone? and other stuff with cancel() etc? + return run(() => ret); + } + } as any; +}; diff --git a/test/functions/.gitignore b/test/functions/.gitignore new file mode 100644 index 000000000..9be0f014f --- /dev/null +++ b/test/functions/.gitignore @@ -0,0 +1,10 @@ +# Compiled JavaScript files +lib/**/*.js +lib/**/*.js.map + +# TypeScript v1 declaration files +typings/ + +# Node.js dependency directory +node_modules/ +*.local \ No newline at end of file diff --git a/test/functions/package-lock.json b/test/functions/package-lock.json new file mode 100644 index 000000000..efcd8b57b --- /dev/null +++ b/test/functions/package-lock.json @@ -0,0 +1,6293 @@ +{ + "name": "functions", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "functions", + "dependencies": { + "firebase-admin": "^12.6.0", + "firebase-functions": "^6.0.1" + }, + "devDependencies": { + "firebase-functions-test": "^3.1.0", + "typescript": "^4.9.0" + }, + "engines": { + "node": "18" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.3.tgz", + "integrity": "sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/core/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==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@babel/generator": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", + "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/types": "^7.26.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.26.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz", + "integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.3", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/traverse/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==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@babel/types": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@fastify/busboy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.1.0.tgz", + "integrity": "sha512-yHmUtGwEbW6HsKpPqT140/L6GpHtquHogRLgtanJFep3UAfDkE0fQfC49U+F9irCAoJVlv3M7VSp4rrtO4LnfA==", + "license": "MIT" + }, + "node_modules/@firebase/app-check-interop-types": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.2.tgz", + "integrity": "sha512-LMs47Vinv2HBMZi49C09dJxp0QT5LwDzFaVGf/+ITHe3BlIhUiLNttkATSXplc89A2lAaeTqjgqVkiRfUGyQiQ==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app-types": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.2.tgz", + "integrity": "sha512-oMEZ1TDlBz479lmABwWsWjzHwheQKiAgnuKxE0pz0IXCVx7/rtlkx1fQ6GfgK24WCrxDKMplZrT50Kh04iMbXQ==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.3.tgz", + "integrity": "sha512-Fc9wuJGgxoxQeavybiuwgyi+0rssr76b+nHpj+eGhXFYAdudMWyfBHvFL/I5fEHniUM/UQdFzi9VXJK2iZF7FQ==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/component": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.9.tgz", + "integrity": "sha512-gm8EUEJE/fEac86AvHn8Z/QW8BvR56TBw3hMW0O838J/1mThYQXAIQBgUv75EqlCZfdawpWLrKt1uXvp9ciK3Q==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.8.tgz", + "integrity": "sha512-dzXALZeBI1U5TXt6619cv0+tgEhJiwlUtQ55WNZY7vGAjv7Q1QioV969iYwt1AQQ0ovHnEW0YW9TiBfefLvErg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/auth-interop-types": "0.2.3", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-1.0.8.tgz", + "integrity": "sha512-OpeWZoPE3sGIRPBKYnW9wLad25RaWbGyk7fFQe4xnJQKRzlynWeFBSRRAoLE2Old01WXwskUiucNqUUVlFsceg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/database": "1.0.8", + "@firebase/database-types": "1.0.5", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.5.tgz", + "integrity": "sha512-fTlqCNwFYyq/C6W7AJ5OCuq5CeZuBEsEwptnVxlNPkWCo5cTTyukzAHRSO/jaQcItz33FfYrrFk1SJofcu2AaQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-types": "0.9.2", + "@firebase/util": "1.10.0" + } + }, + "node_modules/@firebase/logger": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.2.tgz", + "integrity": "sha512-Q1VuA5M1Gjqrwom6I6NUU4lQXdo9IAQieXlujeHZWvRt1b7qQ0KwBaNAjgxG27jgF9/mUwsNmO8ptBCGVYhB0A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/util": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.10.0.tgz", + "integrity": "sha512-xKtx4A668icQqoANRxyDLBLz51TAbDP9KRfpbKGxiCAW346d0BeJe5vN6/hKxxmWwnZ0mautyv39JxviwwQMOQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@google-cloud/firestore": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.11.0.tgz", + "integrity": "sha512-88uZ+jLsp1aVMj7gh3EKYH1aulTAMFAp8sH/v5a9w8q8iqSG27RiWLoxSAFr/XocZ9hGiWH1kEnBw+zl3xAgNA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@opentelemetry/api": "^1.3.0", + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^4.3.3", + "protobufjs": "^7.2.6" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/paginator": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", + "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", + "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", + "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/storage": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.14.0.tgz", + "integrity": "sha512-H41bPL2cMfSi4EEnFzKvg7XSb7T67ocSXrmF7MPjfgFB0L6CKGzfIYJheAZi1iqXjz6XaCT1OBf6HCG5vDBTOQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@google-cloud/paginator": "^5.0.0", + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "^4.0.0", + "abort-controller": "^3.0.0", + "async-retry": "^1.3.3", + "duplexify": "^4.1.3", + "fast-xml-parser": "^4.4.1", + "gaxios": "^6.0.2", + "google-auth-library": "^9.6.3", + "html-entities": "^2.5.2", + "mime": "^3.0.0", + "p-limit": "^3.0.1", + "retry-request": "^7.0.0", + "teeny-request": "^9.0.0", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/storage/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.12.4.tgz", + "integrity": "sha512-NBhrxEWnFh0FxeA0d//YP95lRFsSx2TNLEUQg4/W+5f/BMxcCjgOOIT24iD+ZB/tZw057j44DaIxja7w4XMrhg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz", + "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/lodash": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz", + "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", + "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/qs": { + "version": "6.9.17", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", + "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/request": { + "version": "2.48.12", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", + "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "optional": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 14" + } + }, + "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==", + "dev": true, + "license": "MIT", + "peer": true, + "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==", + "devOptional": true, + "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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "license": "MIT", + "optional": true, + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT", + "optional": true + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "license": "MIT", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "node-int64": "^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==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001687", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001687.tgz", + "integrity": "sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0", + "peer": true + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "peer": true, + "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/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", + "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "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==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT", + "peer": true + }, + "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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "optional": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "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/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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", + "peer": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "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/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", + "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.72", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.72.tgz", + "integrity": "sha512-ZpSAUOZ2Izby7qnZluSrAlGgGQzucmFbN0n64dYzocYxnxV5ufurpj3VgEe4cUp7ir9LmeLxNYo8bVnlM8bQHw==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "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==", + "devOptional": true, + "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.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "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/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "devOptional": true, + "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": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT", + "optional": true + }, + "node_modules/farmhash-modern": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/farmhash-modern/-/farmhash-modern-1.1.0.tgz", + "integrity": "sha512-6ypT4XfgqJk/F3Yuv4SX26I3doUjt0GTG4a+JgWxXQpxXzTBq8fPUeGHfcYMMDPHJHm3yPOSjaeBwBGAHWXCdA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT", + "optional": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fast-xml-parser": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz", + "integrity": "sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "bser": "2.1.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==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/firebase-admin": { + "version": "12.7.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-12.7.0.tgz", + "integrity": "sha512-raFIrOyTqREbyXsNkSHyciQLfv8AUZazehPaQS1lZBSCDYW74FYXU0nQZa3qHI4K+hawohlDbywZ4+qce9YNxA==", + "license": "Apache-2.0", + "dependencies": { + "@fastify/busboy": "^3.0.0", + "@firebase/database-compat": "1.0.8", + "@firebase/database-types": "1.0.5", + "@types/node": "^22.0.1", + "farmhash-modern": "^1.1.0", + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^3.1.0", + "node-forge": "^1.3.1", + "uuid": "^10.0.0" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "@google-cloud/firestore": "^7.7.0", + "@google-cloud/storage": "^7.7.0" + } + }, + "node_modules/firebase-functions": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-6.1.2.tgz", + "integrity": "sha512-1ZKLLOs4YhpzfWOZo0wsqNBusy9113GUfRs89BF6yOlmkxlcJxdJzZEs/ygWeXVJKquZhW2K1Gm10Gir4RJxGQ==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.5", + "@types/express": "^4.17.21", + "cors": "^2.8.5", + "express": "^4.21.0", + "protobufjs": "^7.2.2" + }, + "bin": { + "firebase-functions": "lib/bin/firebase-functions.js" + }, + "engines": { + "node": ">=14.10.0" + }, + "peerDependencies": { + "firebase-admin": "^11.10.0 || ^12.0.0 || ^13.0.0" + } + }, + "node_modules/firebase-functions-test": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/firebase-functions-test/-/firebase-functions-test-3.3.0.tgz", + "integrity": "sha512-X+OOA34MGrsTimFXTDnWT0psAqnmBkJ85bGCoLMwjgei5Prfkqh3bv5QASnXC/cmIVBSF2Qw9uW1+mF/t3kFlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "^4.14.104", + "lodash": "^4.17.5", + "ts-deepmerge": "^2.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "firebase-admin": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0", + "firebase-functions": ">=4.9.0", + "jest": ">=28.0.0" + } + }, + "node_modules/form-data": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.2.tgz", + "integrity": "sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "license": "MIT", + "optional": true + }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "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==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.5.tgz", + "integrity": "sha512-Y4+pKa7XeRUPWFNvOOYHkRYrfzW07oraURSvjDmRVOJ748OrVmeXtpE4+GCEHncjCjkTxPNRt8kEbxDhsn6VTg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "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/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/google-auth-library": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.0.tgz", + "integrity": "sha512-7ccSEJFDFO7exFbO6NRyC+xH8/mZ1GZGG2xxx9iHxZWcjUjJpjWxIMw3cofAKcueZ6DATiukmmprD7yavQHOyQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.4.1.tgz", + "integrity": "sha512-Phyp9fMfA00J3sZbJxbbB4jC55b7DBjE3F6poyL3wKMEBVKA79q6BGuHcTiM28yOzVql0NDbRL8MLLh8Iwk9Dg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@grpc/grpc-js": "^1.10.9", + "@grpc/proto-loader": "^0.7.13", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "google-auth-library": "^9.3.0", + "node-fetch": "^2.7.0", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^2.0.2", + "protobufjs": "^7.3.2", + "retry-request": "^7.0.0", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "optional": true, + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "license": "MIT" + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "optional": true + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "optional": true, + "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", + "optional": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=10.17.0" + } + }, + "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/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8.19" + } + }, + "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.", + "dev": true, + "license": "ISC", + "peer": true, + "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/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.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.12.0" + } + }, + "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==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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", + "peer": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/istanbul-lib-source-maps/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==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "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", + "optional": true, + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken/node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "license": "MIT", + "optional": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwks-rsa": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.1.0.tgz", + "integrity": "sha512-v7nqlfezb9YfHHzYII3ef2a2j1XnGeSE/bK3WfumaYCqONAIstJbrEGapz4kadScZzEt7zYCN7bucj8C0Mv/Rg==", + "license": "MIT", + "dependencies": { + "@types/express": "^4.17.17", + "@types/jsonwebtoken": "^9.0.2", + "debug": "^4.3.4", + "jose": "^4.14.6", + "limiter": "^1.1.5", + "lru-memoizer": "^2.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/jwks-rsa/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/jwks-rsa/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "optional": true, + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "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", + "optional": true + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lru-memoizer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz", + "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==", + "license": "MIT", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "6.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lru-memoizer/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/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "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/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "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==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "optional": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "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==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "devOptional": true, + "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==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT", + "peer": true, + "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", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/proto3-json-serializer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", + "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "protobufjs": "^7.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "peer": true + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/retry-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", + "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/request": "^2.48.8", + "extend": "^3.0.2", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/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", + "peer": true, + "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", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.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==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "license": "MIT", + "optional": true, + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "license": "MIT", + "optional": true + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "devOptional": true, + "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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "license": "MIT", + "optional": true + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "license": "MIT", + "optional": true + }, + "node_modules/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==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/teeny-request": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", + "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.9", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/teeny-request/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/teeny-request/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/teeny-request/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/teeny-request/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "optional": true + }, + "node_modules/teeny-request/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true + }, + "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==", + "dev": true, + "license": "MIT", + "peer": true, + "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/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT", + "optional": true + }, + "node_modules/ts-deepmerge": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-2.0.7.tgz", + "integrity": "sha512-3phiGcxPSSR47RBubQxPoZ+pqXsEsozLo4G4AlSrsMKTFg9TA3l+3he5BqpUi9wiuDbaHWXH/amlzQ49uEdXtg==", + "dev": true, + "license": "ISC" + }, + "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-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "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==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "peer": true, + "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/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT", + "optional": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "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/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "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", + "optional": true + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "optional": true, + "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", + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "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==", + "devOptional": true, + "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/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "devOptional": true, + "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==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/test/functions/package.json b/test/functions/package.json new file mode 100644 index 000000000..dd7606251 --- /dev/null +++ b/test/functions/package.json @@ -0,0 +1,25 @@ +{ + "name": "functions", + "scripts": { + "build": "tsc", + "build:watch": "tsc --watch", + "serve": "npm run build && firebase emulators:start --only functions", + "shell": "npm run build && firebase functions:shell", + "start": "npm run shell", + "deploy": "firebase deploy --only functions", + "logs": "firebase functions:log" + }, + "engines": { + "node": "18" + }, + "main": "lib/index.js", + "dependencies": { + "firebase-admin": "^12.6.0", + "firebase-functions": "^6.0.1" + }, + "devDependencies": { + "typescript": "^4.9.0", + "firebase-functions-test": "^3.1.0" + }, + "private": true +} \ No newline at end of file diff --git a/test/functions/src/index.ts b/test/functions/src/index.ts new file mode 100644 index 000000000..9a1d18e3d --- /dev/null +++ b/test/functions/src/index.ts @@ -0,0 +1,5 @@ +import { onCall } from "firebase-functions/v2/https"; + +export const foo = onCall(() => { + return { bar: "baz" }; +}); diff --git a/test/functions/tsconfig.json b/test/functions/tsconfig.json new file mode 100644 index 000000000..7ce05d039 --- /dev/null +++ b/test/functions/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "module": "commonjs", + "noImplicitReturns": true, + "noUnusedLocals": true, + "outDir": "lib", + "sourceMap": true, + "strict": true, + "target": "es2017" + }, + "compileOnSave": true, + "include": [ + "src" + ] +} diff --git a/test/ng-build/build.sh b/test/ng-build/build.sh deleted file mode 100755 index 886a4d4ab..000000000 --- a/test/ng-build/build.sh +++ /dev/null @@ -1 +0,0 @@ -cd ./test/ng-build/ng9 && yarn && npx ng build --prod diff --git a/test/ng-build/ng9/.editorconfig b/test/ng-build/ng9/.editorconfig deleted file mode 100644 index e89330a61..000000000 --- a/test/ng-build/ng9/.editorconfig +++ /dev/null @@ -1,13 +0,0 @@ -# Editor configuration, see https://editorconfig.org -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -insert_final_newline = true -trim_trailing_whitespace = true - -[*.md] -max_line_length = off -trim_trailing_whitespace = false diff --git a/test/ng-build/ng9/.gitignore b/test/ng-build/ng9/.gitignore deleted file mode 100644 index f4f46a5fe..000000000 --- a/test/ng-build/ng9/.gitignore +++ /dev/null @@ -1,46 +0,0 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. - -# compiled output -/dist -/tmp -/out-tsc -# Only exists if Bazel was run -/bazel-out - -# dependencies -/node_modules - -# profiling files -chrome-profiler-events.json -speed-measure-plugin.json - -# IDEs and editors -/.idea -.project -.classpath -.c9/ -*.launch -.settings/ -*.sublime-workspace - -# IDE - VSCode -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -.history/* - -# misc -/.sass-cache -/connect.lock -/coverage -/libpeerconnection.log -npm-debug.log -yarn-error.log -testem.log -/typings - -# System Files -.DS_Store -Thumbs.db diff --git a/test/ng-build/ng9/README.md b/test/ng-build/ng9/README.md deleted file mode 100644 index 9380758c9..000000000 --- a/test/ng-build/ng9/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# Ng8 - -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.0.0. - -## Development server - -Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. - -## Code scaffolding - -Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. - -## Build - -Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. - -## Running unit tests - -Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). - -## Running end-to-end tests - -Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). - -## Further help - -To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). diff --git a/test/ng-build/ng9/angular.json b/test/ng-build/ng9/angular.json deleted file mode 100644 index 583e9a473..000000000 --- a/test/ng-build/ng9/angular.json +++ /dev/null @@ -1,124 +0,0 @@ -{ - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "ng8": { - "projectType": "application", - "schematics": {}, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "aot": true, - "outputPath": "dist/ng8", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.css" - ], - "scripts": [] - }, - "configurations": { - "production": { - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" - } - ], - "optimization": true, - "outputHashing": "all", - "sourceMap": false, - "extractCss": true, - "namedChunks": false, - "aot": true, - "extractLicenses": true, - "vendorChunk": false, - "buildOptimizer": true, - "budgets": [ - { - "type": "initial", - "maximumWarning": "2mb", - "maximumError": "5mb" - }, - { - "type": "anyComponentStyle", - "maximumWarning": "6kb" - } - ] - } - } - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "options": { - "browserTarget": "ng8:build" - }, - "configurations": { - "production": { - "browserTarget": "ng8:build:production" - } - } - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "ng8:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.css" - ], - "scripts": [] - } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": [ - "tsconfig.app.json", - "tsconfig.spec.json", - "e2e/tsconfig.json" - ], - "exclude": [ - "**/node_modules/**" - ] - } - }, - "e2e": { - "builder": "@angular-devkit/build-angular:protractor", - "options": { - "protractorConfig": "e2e/protractor.conf.js", - "devServerTarget": "ng8:serve" - }, - "configurations": { - "production": { - "devServerTarget": "ng8:serve:production" - } - } - } - } - }}, - "defaultProject": "ng8" -} \ No newline at end of file diff --git a/test/ng-build/ng9/browserslist b/test/ng-build/ng9/browserslist deleted file mode 100644 index 80848532e..000000000 --- a/test/ng-build/ng9/browserslist +++ /dev/null @@ -1,12 +0,0 @@ -# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. -# For additional information regarding the format and rule options, please see: -# https://github.com/browserslist/browserslist#queries - -# You can see what browsers were selected by your queries by running: -# npx browserslist - -> 0.5% -last 2 versions -Firefox ESR -not dead -not IE 9-11 # For IE 9-11 support, remove 'not'. \ No newline at end of file diff --git a/test/ng-build/ng9/e2e/protractor.conf.js b/test/ng-build/ng9/e2e/protractor.conf.js deleted file mode 100644 index 73e4e6806..000000000 --- a/test/ng-build/ng9/e2e/protractor.conf.js +++ /dev/null @@ -1,32 +0,0 @@ -// @ts-check -// Protractor configuration file, see link for more information -// https://github.com/angular/protractor/blob/master/lib/config.ts - -const { SpecReporter } = require('jasmine-spec-reporter'); - -/** - * @type { import("protractor").Config } - */ -exports.config = { - allScriptsTimeout: 11000, - specs: [ - './src/**/*.e2e-spec.ts' - ], - capabilities: { - 'browserName': 'chrome' - }, - directConnect: true, - baseUrl: 'http://localhost:4200/', - framework: 'jasmine', - jasmineNodeOpts: { - showColors: true, - defaultTimeoutInterval: 30000, - print: function() {} - }, - onPrepare() { - require('ts-node').register({ - project: require('path').join(__dirname, './tsconfig.json') - }); - jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); - } -}; \ No newline at end of file diff --git a/test/ng-build/ng9/e2e/src/app.e2e-spec.ts b/test/ng-build/ng9/e2e/src/app.e2e-spec.ts deleted file mode 100644 index 4ea217cf8..000000000 --- a/test/ng-build/ng9/e2e/src/app.e2e-spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { AppPage } from './app.po'; -import { browser, logging } from 'protractor'; - -describe('workspace-project App', () => { - let page: AppPage; - - beforeEach(() => { - page = new AppPage(); - }); - - it('should display welcome message', () => { - page.navigateTo(); - expect(page.getTitleText()).toEqual('Welcome to ng8!'); - }); - - afterEach(async () => { - // Assert that there are no errors emitted from the browser - const logs = await browser.manage().logs().get(logging.Type.BROWSER); - expect(logs).not.toContain(jasmine.objectContaining({ - level: logging.Level.SEVERE, - } as logging.Entry)); - }); -}); diff --git a/test/ng-build/ng9/e2e/src/app.po.ts b/test/ng-build/ng9/e2e/src/app.po.ts deleted file mode 100644 index 5776aa9eb..000000000 --- a/test/ng-build/ng9/e2e/src/app.po.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { browser, by, element } from 'protractor'; - -export class AppPage { - navigateTo() { - return browser.get(browser.baseUrl) as Promise; - } - - getTitleText() { - return element(by.css('app-root h1')).getText() as Promise; - } -} diff --git a/test/ng-build/ng9/e2e/tsconfig.json b/test/ng-build/ng9/e2e/tsconfig.json deleted file mode 100644 index 39b800f78..000000000 --- a/test/ng-build/ng9/e2e/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../out-tsc/e2e", - "module": "commonjs", - "target": "es5", - "types": [ - "jasmine", - "jasminewd2", - "node" - ] - } -} diff --git a/test/ng-build/ng9/karma.conf.js b/test/ng-build/ng9/karma.conf.js deleted file mode 100644 index f4e4f3c3b..000000000 --- a/test/ng-build/ng9/karma.conf.js +++ /dev/null @@ -1,32 +0,0 @@ -// Karma configuration file, see link for more information -// https://karma-runner.github.io/1.0/config/configuration-file.html - -module.exports = function (config) { - config.set({ - basePath: '', - frameworks: ['jasmine', '@angular-devkit/build-angular'], - plugins: [ - require('karma-jasmine'), - require('karma-chrome-launcher'), - require('karma-jasmine-html-reporter'), - require('karma-coverage-istanbul-reporter'), - require('@angular-devkit/build-angular/plugins/karma') - ], - client: { - clearContext: false // leave Jasmine Spec Runner output visible in browser - }, - coverageIstanbulReporter: { - dir: require('path').join(__dirname, './coverage/ng8'), - reports: ['html', 'lcovonly', 'text-summary'], - fixWebpackSourcePaths: true - }, - reporters: ['progress', 'kjhtml'], - port: 9876, - colors: true, - logLevel: config.LOG_INFO, - autoWatch: true, - browsers: ['Chrome'], - singleRun: false, - restartOnFileChange: true - }); -}; diff --git a/test/ng-build/ng9/package.json b/test/ng-build/ng9/package.json deleted file mode 100644 index 27983e296..000000000 --- a/test/ng-build/ng9/package.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "name": "ng8", - "version": "0.0.0", - "scripts": { - "ng": "ng", - "postinstall": "ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points", - "start": "ng serve", - "build": "ng build", - "build:prod": "ng build --prod", - "test": "ng test", - "lint": "ng lint", - "e2e": "ng e2e" - }, - "private": true, - "dependencies": { - "@angular/animations": "~9.0.0-rc.12", - "@angular/common": "~9.0.0-rc.12", - "@angular/compiler": "~9.0.0-rc.12", - "@angular/core": "~9.0.0-rc.12", - "@angular/fire": "../../../dist/packages-dist", - "@angular/forms": "~9.0.0-rc.12", - "@angular/platform-browser": "~9.0.0-rc.12", - "@angular/platform-browser-dynamic": "~9.0.0-rc.12", - "@angular/router": "~9.0.0-rc.12", - "firebase": "^7.8.0", - "rxjs": "~6.5.3", - "tslib": "^1.10.0", - "zone.js": "~0.10.2" - }, - "devDependencies": { - "@angular-devkit/build-angular": "~0.900.0-rc.11", - "@angular/cli": "~9.0.0-rc.11", - "@angular/compiler-cli": "~9.0.0-rc.12", - "@angular/language-service": "~9.0.0-rc.12", - "@types/jasmine": "~3.3.8", - "@types/jasminewd2": "~2.0.3", - "@types/node": "^12.11.1", - "codelyzer": "^5.1.2", - "jasmine-core": "~3.4.0", - "jasmine-spec-reporter": "~4.2.1", - "karma": "~4.1.0", - "karma-chrome-launcher": "~2.2.0", - "karma-coverage-istanbul-reporter": "~2.0.1", - "karma-jasmine": "~2.0.1", - "karma-jasmine-html-reporter": "^1.4.0", - "protractor": "~5.4.0", - "ts-node": "~7.0.0", - "tslint": "~5.15.0", - "typescript": "~3.6.4" - } -} \ No newline at end of file diff --git a/test/ng-build/ng9/src/app/app.component.css b/test/ng-build/ng9/src/app/app.component.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/ng-build/ng9/src/app/app.component.html b/test/ng-build/ng9/src/app/app.component.html deleted file mode 100644 index 5226d57f5..000000000 --- a/test/ng-build/ng9/src/app/app.component.html +++ /dev/null @@ -1,20 +0,0 @@ - -
-

- Welcome to {{ title }}! -

- Angular Logo -
-

Here are some links to help you start:

- - diff --git a/test/ng-build/ng9/src/app/app.component.spec.ts b/test/ng-build/ng9/src/app/app.component.spec.ts deleted file mode 100644 index dd8cd7669..000000000 --- a/test/ng-build/ng9/src/app/app.component.spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { TestBed, async } from '@angular/core/testing'; -import { AppComponent } from './app.component'; - -describe('AppComponent', () => { - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ - AppComponent - ], - }).compileComponents(); - })); - - it('should create the app', () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.debugElement.componentInstance; - expect(app).toBeTruthy(); - }); - - it(`should have as title 'ng8'`, () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.debugElement.componentInstance; - expect(app.title).toEqual('ng8'); - }); - - it('should render title in a h1 tag', () => { - const fixture = TestBed.createComponent(AppComponent); - fixture.detectChanges(); - const compiled = fixture.debugElement.nativeElement; - expect(compiled.querySelector('h1').textContent).toContain('Welcome to ng8!'); - }); -}); diff --git a/test/ng-build/ng9/src/app/app.component.ts b/test/ng-build/ng9/src/app/app.component.ts deleted file mode 100644 index 719421471..000000000 --- a/test/ng-build/ng9/src/app/app.component.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Component, ApplicationRef } from '@angular/core'; -import { AngularFireAnalytics } from '@angular/fire/analytics'; -import { FirebaseApp } from '@angular/fire'; -import { AngularFireDatabase } from '@angular/fire/database'; -import { AngularFireAuth } from '@angular/fire/auth'; -import { AngularFirestore } from '@angular/fire/firestore'; -import { AngularFireStorage } from '@angular/fire/storage'; -import { AngularFireMessaging } from '@angular/fire/messaging'; -import { AngularFireFunctions } from '@angular/fire/functions'; -import { AngularFirePerformance } from '@angular/fire/performance'; -import { canActivate, loggedIn } from '@angular/fire/auth-guard'; -import { AngularFireRemoteConfig } from '@angular/fire/remote-config'; - -import 'firebase/database'; -import 'firebase/firestore'; -import 'firebase/storage'; - -@Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.css'] -}) -export class AppComponent { - title = 'ng8'; - constructor( - private readonly analytics: AngularFireAnalytics, - private readonly app: FirebaseApp, - private readonly db: AngularFireDatabase, - private readonly auth: AngularFireAuth, - private readonly afStore: AngularFirestore, - private readonly storage: AngularFireStorage, - private readonly messaging: AngularFireMessaging, - private readonly functions: AngularFireFunctions, - private readonly performance: AngularFirePerformance, - private readonly remoteConfig: AngularFireRemoteConfig, - private readonly appRef: ApplicationRef - ) { - const authArgs = canActivate(() => loggedIn); - console.log(analytics, app, db, auth, afStore, storage, messaging, functions, remoteConfig, performance, authArgs); - appRef.isStable.subscribe(it => console.log('isStable', it)); - } -} diff --git a/test/ng-build/ng9/src/app/app.module.ts b/test/ng-build/ng9/src/app/app.module.ts deleted file mode 100644 index 3f03371cc..000000000 --- a/test/ng-build/ng9/src/app/app.module.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { BrowserModule } from '@angular/platform-browser'; -import { NgModule } from '@angular/core'; - -import { AppComponent } from './app.component'; - -import { FirebaseApp, AngularFireModule } from '@angular/fire'; -import { AngularFireAnalyticsModule } from '@angular/fire/analytics'; -import { AngularFireDatabaseModule } from '@angular/fire/database'; -import { AngularFireAuthModule } from '@angular/fire/auth'; -import { AngularFirestoreModule } from '@angular/fire/firestore'; -import { AngularFireStorageModule } from '@angular/fire/storage'; -import { AngularFireMessagingModule } from '@angular/fire/messaging'; -import { AngularFireFunctionsModule } from '@angular/fire/functions'; -import { AngularFireRemoteConfigModule } from '@angular/fire/remote-config'; -import { AngularFirePerformanceModule, AUTOMATICALLY_TRACE_CORE_NG_METRICS } from '@angular/fire/performance'; -import { AngularFireAuthGuardModule } from '@angular/fire/auth-guard'; - - -@NgModule({ - declarations: [ - AppComponent - ], - imports: [ - BrowserModule, - AngularFireModule.initializeApp({ - apiKey: 'AIzaSyBVSy3YpkVGiKXbbxeK0qBnu3-MNZ9UIjA', - authDomain: 'angularfire2-test.firebaseapp.com', - databaseURL: 'https://angularfire2-test.firebaseio.com', - projectId: 'angularfire2-test', - storageBucket: 'angularfire2-test.appspot.com', - messagingSenderId: '920323787688', - appId: '1:920323787688:web:2253a0e5eb5b9a8b', - databaseName: 'angularfire2-test', - measurementId: 'G-W20QDV5CZP' - }), - AngularFireAnalyticsModule, - AngularFireAuthModule, - AngularFireDatabaseModule, - AngularFirestoreModule, - AngularFireStorageModule, - AngularFireMessagingModule, - AngularFireFunctionsModule, - AngularFireRemoteConfigModule, - AngularFirePerformanceModule, - AngularFireAuthGuardModule - ], - providers: [], - bootstrap: [AppComponent] -}) -export class AppModule { } diff --git a/test/ng-build/ng9/src/assets/.gitkeep b/test/ng-build/ng9/src/assets/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/ng-build/ng9/src/environments/environment.prod.ts b/test/ng-build/ng9/src/environments/environment.prod.ts deleted file mode 100644 index 3612073bc..000000000 --- a/test/ng-build/ng9/src/environments/environment.prod.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const environment = { - production: true -}; diff --git a/test/ng-build/ng9/src/environments/environment.ts b/test/ng-build/ng9/src/environments/environment.ts deleted file mode 100644 index 7b4f817ad..000000000 --- a/test/ng-build/ng9/src/environments/environment.ts +++ /dev/null @@ -1,16 +0,0 @@ -// This file can be replaced during build by using the `fileReplacements` array. -// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. -// The list of file replacements can be found in `angular.json`. - -export const environment = { - production: false -}; - -/* - * For easier debugging in development mode, you can import the following file - * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. - * - * This import should be commented out in production mode because it will have a negative impact - * on performance if an error is thrown. - */ -// import 'zone.js/dist/zone-error'; // Included with Angular CLI. diff --git a/test/ng-build/ng9/src/favicon.ico b/test/ng-build/ng9/src/favicon.ico deleted file mode 100644 index 8081c7cea..000000000 Binary files a/test/ng-build/ng9/src/favicon.ico and /dev/null differ diff --git a/test/ng-build/ng9/src/index.html b/test/ng-build/ng9/src/index.html deleted file mode 100644 index 12f21fd68..000000000 --- a/test/ng-build/ng9/src/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - Ng8 - - - - - - - - - diff --git a/test/ng-build/ng9/src/main.ts b/test/ng-build/ng9/src/main.ts deleted file mode 100644 index c7b673cf4..000000000 --- a/test/ng-build/ng9/src/main.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { enableProdMode } from '@angular/core'; -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; - -import { AppModule } from './app/app.module'; -import { environment } from './environments/environment'; - -if (environment.production) { - enableProdMode(); -} - -platformBrowserDynamic().bootstrapModule(AppModule) - .catch(err => console.error(err)); diff --git a/test/ng-build/ng9/src/polyfills.ts b/test/ng-build/ng9/src/polyfills.ts deleted file mode 100644 index aa665d6b8..000000000 --- a/test/ng-build/ng9/src/polyfills.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * This file includes polyfills needed by Angular and is loaded before the app. - * You can add your own extra polyfills to this file. - * - * This file is divided into 2 sections: - * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. - * 2. Application imports. Files imported after ZoneJS that should be loaded before your main - * file. - * - * The current setup is for so-called "evergreen" browsers; the last versions of browsers that - * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), - * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. - * - * Learn more in https://angular.io/guide/browser-support - */ - -/*************************************************************************************************** - * BROWSER POLYFILLS - */ - -/** IE10 and IE11 requires the following for NgClass support on SVG elements */ -// import 'classlist.js'; // Run `npm install --save classlist.js`. - -/** - * Web Animations `@angular/platform-browser/animations` - * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. - * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). - */ -// import 'web-animations-js'; // Run `npm install --save web-animations-js`. - -/** - * By default, zone.js will patch all possible macroTask and DomEvents - * user can disable parts of macroTask/DomEvents patch by setting following flags - * because those flags need to be set before `zone.js` being loaded, and webpack - * will put import in the top of bundle, so user need to create a separate file - * in this directory (for example: zone-flags.ts), and put the following flags - * into that file, and then add the following code before importing zone.js. - * import './zone-flags.ts'; - * - * The flags allowed in zone-flags.ts are listed here. - * - * The following flags will work for all browsers. - * - * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame - * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick - * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames - * - * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js - * with the following flag, it will bypass `zone.js` patch for IE/Edge - * - * (window as any).__Zone_enable_cross_context_check = true; - * - */ - -/*************************************************************************************************** - * Zone JS is required by default for Angular itself. - */ -import 'zone.js/dist/zone'; // Included with Angular CLI. - - -/*************************************************************************************************** - * APPLICATION IMPORTS - */ diff --git a/test/ng-build/ng9/src/styles.css b/test/ng-build/ng9/src/styles.css deleted file mode 100644 index 90d4ee007..000000000 --- a/test/ng-build/ng9/src/styles.css +++ /dev/null @@ -1 +0,0 @@ -/* You can add global styles to this file, and also import other style files */ diff --git a/test/ng-build/ng9/src/test.ts b/test/ng-build/ng9/src/test.ts deleted file mode 100644 index 16317897b..000000000 --- a/test/ng-build/ng9/src/test.ts +++ /dev/null @@ -1,20 +0,0 @@ -// This file is required by karma.conf.js and loads recursively all the .spec and framework files - -import 'zone.js/dist/zone-testing'; -import { getTestBed } from '@angular/core/testing'; -import { - BrowserDynamicTestingModule, - platformBrowserDynamicTesting -} from '@angular/platform-browser-dynamic/testing'; - -declare const require: any; - -// First, initialize the Angular testing environment. -getTestBed().initTestEnvironment( - BrowserDynamicTestingModule, - platformBrowserDynamicTesting() -); -// Then we find all the tests. -const context = require.context('./', true, /\.spec\.ts$/); -// And load the modules. -context.keys().map(context); diff --git a/test/ng-build/ng9/tsconfig.app.json b/test/ng-build/ng9/tsconfig.app.json deleted file mode 100644 index c9bfd72d1..000000000 --- a/test/ng-build/ng9/tsconfig.app.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/app", - "types": [] - }, - "files": [ - "src/main.ts", - "src/polyfills.ts" - ], - "include": [ - "src/**/*.d.ts" - ], - "angularCompilerOptions": { - "enableIvy": true - } -} diff --git a/test/ng-build/ng9/tsconfig.json b/test/ng-build/ng9/tsconfig.json deleted file mode 100644 index 6ec9ceb17..000000000 --- a/test/ng-build/ng9/tsconfig.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "compileOnSave": false, - "compilerOptions": { - "baseUrl": "./", - "outDir": "./dist/out-tsc", - "sourceMap": true, - "declaration": false, - "module": "esnext", - "moduleResolution": "node", - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "importHelpers": true, - "target": "es2015", - "typeRoots": [ - "node_modules/@types" - ], - "lib": [ - "es2018", - "dom" - ] - } -} diff --git a/test/ng-build/ng9/tsconfig.spec.json b/test/ng-build/ng9/tsconfig.spec.json deleted file mode 100644 index 6400fde7d..000000000 --- a/test/ng-build/ng9/tsconfig.spec.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/spec", - "types": [ - "jasmine", - "node" - ] - }, - "files": [ - "src/test.ts", - "src/polyfills.ts" - ], - "include": [ - "src/**/*.spec.ts", - "src/**/*.d.ts" - ] -} diff --git a/test/ng-build/ng9/tslint.json b/test/ng-build/ng9/tslint.json deleted file mode 100644 index 188bd78d3..000000000 --- a/test/ng-build/ng9/tslint.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "extends": "tslint:recommended", - "rules": { - "array-type": false, - "arrow-parens": false, - "deprecation": { - "severity": "warn" - }, - "component-class-suffix": true, - "contextual-lifecycle": true, - "directive-class-suffix": true, - "directive-selector": [ - true, - "attribute", - "app", - "camelCase" - ], - "component-selector": [ - true, - "element", - "app", - "kebab-case" - ], - "import-blacklist": [ - true, - "rxjs/Rx" - ], - "interface-name": false, - "max-classes-per-file": false, - "max-line-length": [ - true, - 140 - ], - "member-access": false, - "member-ordering": [ - true, - { - "order": [ - "static-field", - "instance-field", - "static-method", - "instance-method" - ] - } - ], - "no-consecutive-blank-lines": false, - "no-console": [ - true, - "debug", - "info", - "time", - "timeEnd", - "trace" - ], - "no-empty": false, - "no-inferrable-types": [ - true, - "ignore-params" - ], - "no-non-null-assertion": true, - "no-redundant-jsdoc": true, - "no-switch-case-fall-through": true, - "no-use-before-declare": true, - "no-var-requires": false, - "object-literal-key-quotes": [ - true, - "as-needed" - ], - "object-literal-sort-keys": false, - "ordered-imports": false, - "quotemark": [ - true, - "single" - ], - "trailing-comma": false, - "no-conflicting-lifecycle": true, - "no-host-metadata-property": true, - "no-input-rename": true, - "no-inputs-metadata-property": true, - "no-output-native": true, - "no-output-on-prefix": true, - "no-output-rename": true, - "no-outputs-metadata-property": true, - "template-banana-in-box": true, - "template-no-negated-async": true, - "use-lifecycle-interface": true, - "use-pipe-transform-interface": true - }, - "rulesDirectory": [ - "codelyzer" - ] -} \ No newline at end of file diff --git a/test/storage.rules b/test/storage.rules index d494542e9..a7db6961c 100644 --- a/test/storage.rules +++ b/test/storage.rules @@ -1,7 +1,8 @@ +rules_version = '2'; service firebase.storage { match /b/{bucket}/o { match /{allPaths=**} { - allow read, write: if request.auth!=null; + allow read, write: if true; } } } diff --git a/test/typings-test/index.ts b/test/typings-test/index.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/typings-test/package.sample.json b/test/typings-test/package.sample.json deleted file mode 100644 index 52563ed57..000000000 --- a/test/typings-test/package.sample.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "typings-test", - "version": "1.0.0", - "private": true, - "dependencies": { - "@angular/firebase": "file:{{ANGULARFIRE_VERSION}}", - "firebase": "{{FIREBASE_VERSION}}", - "@angular/common": "{{ANGULAR_VERSION}}", - "@angular/compiler": "{{ANGULAR_VERSION}}", - "@angular/core": "{{ANGULAR_VERSION}}", - "@angular/platform-browser": "{{ANGULAR_VERSION}}", - "@angular/platform-browser-dynamic": "{{ANGULAR_VERSION}}", - "zone.js": "{{ZONE_VERSION}}", - "rxjs": "{{RXJS_VERSION}}", - "typescript": "{{TYPESCRIPT_VERSION}}" - } -} diff --git a/test/typings-test/tsconfig-test.json b/test/typings-test/tsconfig-test.json deleted file mode 100644 index 645b6421c..000000000 --- a/test/typings-test/tsconfig-test.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "lib": ["es6", "dom"], - "module": "es6", - "target": "es5", - "moduleResolution": "node", - "noImplicitAny": true, - "skipLibCheck": false, - "skipDefaultLibCheck": false, - "noEmit": true - }, - "files": [ - "index.ts" - ] -} diff --git a/tools/build.sh b/tools/build.sh index a159fb7eb..f92330a27 100755 --- a/tools/build.sh +++ b/tools/build.sh @@ -1,11 +1,21 @@ -yarn +SHORT_SHA=$(git rev-parse --short $GITHUB_SHA) +TAG_TEST="^refs/tags/.+$" +LATEST_TEST="^[^-]*$" -if test $TAG_NAME; then - export VERSION=$TAG_NAME +if [[ $GITHUB_REF =~ $TAG_TEST ]]; then + OVERRIDE_VERSION=${GITHUB_REF/refs\/tags\//} + if [[ $OVERRIDE_VERSION =~ $LATEST_TEST ]]; then + NPM_TAG=latest + else + NPM_TAG=next + fi; else - export VERSION=$(npm version | sed -n "s/. '@angular\/fire': '\(.*\)',/\1/p")-canary.$SHORT_SHA -fi + OVERRIDE_VERSION=$(node -e "console.log(require('./package.json').version)")-canary.$SHORT_SHA + NPM_TAG=canary +fi; -echo $VERSION && -npm version $VERSION --allow-same-version && -yarn build +npm --no-git-tag-version --allow-same-version -f version $OVERRIDE_VERSION + +npm run build && + echo "npm publish . --access public --registry https://wombat-dressing-room.appspot.com --tag $NPM_TAG" > ./dist/packages-dist/publish.sh && + chmod +x ./dist/packages-dist/publish.sh diff --git a/tools/build.ts b/tools/build.ts index ae2883156..c0943f381 100644 --- a/tools/build.ts +++ b/tools/build.ts @@ -1,17 +1,260 @@ -import { spawn } from 'child_process'; -import { copy, readFile, writeFile } from 'fs-extra'; -import { prettySize } from 'pretty-size'; -import { sync as gzipSync } from 'gzip-size'; -import { dirname, join } from 'path'; +import { spawn } from 'cross-spawn'; +import { copy, writeFile } from 'fs-extra'; +import { join, sep } from 'path'; +import { keys as tsKeys } from 'ts-transformer-keys'; +import * as esbuild from "esbuild"; -// TODO infer these from the package.json -const MODULES = [ - 'core', 'analytics', 'auth', 'auth-guard', 'database', - 'firestore', 'functions', 'remote-config', - 'storage', 'messaging', 'performance' -]; -const UMD_NAMES = MODULES.map(m => m === 'core' ? 'angular-fire' : `angular-fire-${m}`); -const ENTRY_NAMES = MODULES.map(m => m === 'core' ? '@angular/fire' : `@angular/fire/${m}`); +enum LogLevel { + "WARN" = 1, + "VERBOSE" = 2, +}; + +interface OverrideOptions { + exportName?: string; + zoneWrap?: boolean; + blockUntilFirst?: boolean; + override?: boolean; + logLevel?: LogLevel; +} + +const firestoreOverrides = { + addDoc: { logLevel: LogLevel.VERBOSE }, + aggregateFieldEqual: { logLevel: LogLevel.VERBOSE }, + aggregateQuerySnapshotEqual: { logLevel: LogLevel.VERBOSE }, + and: { logLevel: LogLevel.VERBOSE }, + arrayRemove: null, + arrayUnion: null, + average: null, + collection: { logLevel: LogLevel.VERBOSE }, + collectionGroup: { logLevel: LogLevel.VERBOSE }, + count: null, + deleteDoc: { logLevel: LogLevel.VERBOSE }, + deleteField: { logLevel: LogLevel.VERBOSE }, + doc: { logLevel: LogLevel.VERBOSE }, + documentId: { logLevel: LogLevel.VERBOSE }, + endAt: { logLevel: LogLevel.VERBOSE }, + endBefore: { logLevel: LogLevel.VERBOSE }, + increment: { logLevel: LogLevel.VERBOSE }, + limit: { logLevel: LogLevel.VERBOSE }, + limitToLast: { logLevel: LogLevel.VERBOSE }, + memoryEagerGarbageCollector: null, + memoryLocalCache: null, + memoryLruGarbageCollector: null, + namedQuery: { logLevel: LogLevel.VERBOSE }, + or: { logLevel: LogLevel.VERBOSE }, + orderBy: { logLevel: LogLevel.VERBOSE }, + persistentLocalCache: null, + persistentMultipleTabManager: null, + persistentSingleTabManager: null, + query: { logLevel: LogLevel.VERBOSE }, + queryEqual: { logLevel: LogLevel.VERBOSE }, + refEqual: { logLevel: LogLevel.VERBOSE }, + serverTimestamp: null, + setDoc: { logLevel: LogLevel.VERBOSE }, + snapshotEqual: { logLevel: LogLevel.VERBOSE }, + startAfter: { logLevel: LogLevel.VERBOSE }, + startAt: { logLevel: LogLevel.VERBOSE }, + sum: { logLevel: LogLevel.VERBOSE }, + updateDoc: { logLevel: LogLevel.VERBOSE }, + vector: { logLevel: LogLevel.VERBOSE }, + where: { logLevel: LogLevel.VERBOSE }, + writeBatch: { logLevel: LogLevel.VERBOSE }, +}; + +function zoneWrapExports() { + const reexport = async ( + module: string, + name: string, + path: string, + exports: string[], + overrides: Record = {} + ) => { + const imported = await import(path); + const toBeExported: [string, string, boolean][] = exports.sort(). + filter(it => !it.startsWith('_') && overrides[it] !== null && overrides[it]?.override !== true). + map(importName => { + const zoneWrap = typeof imported[importName] === 'function' && + // eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with + (overrides[importName]?.zoneWrap ?? importName[0] !== importName[0].toUpperCase()); + const exportName = overrides[importName]?.exportName ?? importName; + return [importName, exportName, zoneWrap]; + }); + const zoneWrapped = toBeExported.filter(([, , zoneWrap]) => zoneWrap); + const rawExport = toBeExported.filter(([, , zoneWrap]) => !zoneWrap); + const overridden = Object.keys(overrides).filter(key => overrides[key]?.override); + const isFirebaseSDK = path.startsWith('firebase/'); + const hasZoneWrappedFns = zoneWrapped.length > 0; + const hasRawExportedFns = rawExport.length > 0; + const hasOverridedFns = overridden.length > 0; + const zoneWrappedImports = zoneWrapped.map(([importName]) => `${importName} as _${importName}`).join(',\n '); + const rawExportedFns = rawExport.map(([importName, exportName]) => + `${importName}${exportName === importName ? '' : `as ${exportName}`}`).join(',\n '); + const overriddenFns = overridden.join(',\n '); + const exportedZoneWrappedFns = zoneWrapped.map(([importName, exportName]) => + `export const ${exportName} = ÉĩzoneWrap(_${importName}, ${overrides[importName]?.blockUntilFirst ?? true}${overrides[importName]?.logLevel ? `, ${overrides[importName].logLevel}` : ""});`) + .join('\n'); + const filePath = join(process.cwd(), 'src', `${module}/${name}.ts`); + // TODO(davideast): Create a builder pattern for this file for readability + const fileOutput = `// DO NOT MODIFY, this file is autogenerated by tools/build.ts +${isFirebaseSDK ? `export * from '${path}';\n` : ''}${hasZoneWrappedFns ? `import { ÉĩzoneWrap } from '@angular/fire'; +import { + ${zoneWrappedImports} +} from '${path}'; +` : ''}${!isFirebaseSDK && hasRawExportedFns ? ` +export { + ${rawExportedFns} +} from '${path}'; +` : ''}${hasOverridedFns ? ` +export { + ${overriddenFns} +} from './overrides'; +` : ''} +${exportedZoneWrappedFns} +`; + await writeFile(filePath, fileOutput); + }; + return Promise.all([ + reexport('ai', 'firebase', 'firebase/ai', tsKeys()), + reexport('analytics', 'firebase', 'firebase/analytics', tsKeys(), { + isSupported: { blockUntilFirst: false }, + logEvent: { blockUntilFirst: false, logLevel: LogLevel.VERBOSE }, + setAnalyticsCollectionEnabled: { logLevel: LogLevel.VERBOSE }, + setConsent: { logLevel: LogLevel.VERBOSE }, + setCurrentScreen: { logLevel: LogLevel.VERBOSE }, + setDefaultEventParameters: { logLevel: LogLevel.VERBOSE }, + setUserId: { logLevel: LogLevel.VERBOSE }, + setUserProperties: { logLevel: LogLevel.VERBOSE }, + }), + reexport('app', 'firebase', 'firebase/app', tsKeys()), + reexport('app-check', 'firebase', 'firebase/app-check', tsKeys(), { + getLimitedUseToken: { logLevel: LogLevel.VERBOSE }, + }), + reexport('auth', 'rxfire', 'rxfire/auth', tsKeys()), + reexport('auth', 'firebase', 'firebase/auth', tsKeys(), { + debugErrorMap: null, + inMemoryPersistence: null, + browserLocalPersistence: null, + browserSessionPersistence: null, + indexedDBLocalPersistence: null, + prodErrorMap: null, + multiFactor: null, + linkWithCredential: { logLevel: LogLevel.VERBOSE }, + linkWithPhoneNumber: { logLevel: LogLevel.VERBOSE }, + linkWithPopup: { logLevel: LogLevel.VERBOSE }, + linkWithRedirect: { logLevel: LogLevel.VERBOSE }, + signInAnonymously: { logLevel: LogLevel.VERBOSE }, + signInWithCredential: { logLevel: LogLevel.VERBOSE }, + signInWithCustomToken: { logLevel: LogLevel.VERBOSE }, + signInWithEmailAndPassword: { logLevel: LogLevel.VERBOSE }, + signInWithEmailLink: { logLevel: LogLevel.VERBOSE }, + signInWithPhoneNumber: { logLevel: LogLevel.VERBOSE }, + signInWithPopup: { logLevel: LogLevel.VERBOSE }, + signInWithRedirect: { logLevel: LogLevel.VERBOSE }, + signOut: { logLevel: LogLevel.VERBOSE }, + confirmPasswordReset: { logLevel: LogLevel.VERBOSE }, + deleteUser: { logLevel: LogLevel.VERBOSE }, + createUserWithEmailAndPassword: { logLevel: LogLevel.VERBOSE }, + fetchSignInMethodsForEmail: { logLevel: LogLevel.VERBOSE }, + getAdditionalUserInfo: { logLevel: LogLevel.VERBOSE }, + reauthenticateWithCredential: { logLevel: LogLevel.VERBOSE }, + reauthenticateWithPhoneNumber: { logLevel: LogLevel.VERBOSE }, + reauthenticateWithPopup: { logLevel: LogLevel.VERBOSE }, + reauthenticateWithRedirect: { logLevel: LogLevel.VERBOSE }, + reload : { logLevel: LogLevel.VERBOSE }, + revokeAccessToken: { logLevel: LogLevel.VERBOSE }, + sendEmailVerification: { logLevel: LogLevel.VERBOSE }, + sendPasswordResetEmail: { logLevel: LogLevel.VERBOSE }, + sendSignInLinkToEmail: { logLevel: LogLevel.VERBOSE }, + unlink: { logLevel: LogLevel.VERBOSE }, + updateCurrentUser: { logLevel: LogLevel.VERBOSE }, + updateEmail: { logLevel: LogLevel.VERBOSE }, + updatePassword: { logLevel: LogLevel.VERBOSE }, + updatePhoneNumber: { logLevel: LogLevel.VERBOSE }, + updateProfile: { logLevel: LogLevel.VERBOSE }, + useDeviceLanguage: { logLevel: LogLevel.VERBOSE }, + validatePassword: { logLevel: LogLevel.VERBOSE }, + verifyBeforeUpdateEmail: { logLevel: LogLevel.VERBOSE }, + verifyPasswordResetCode: { logLevel: LogLevel.VERBOSE }, + }), + reexport('database', 'rxfire', 'rxfire/database', tsKeys()), + reexport('database', 'firebase', 'firebase/database', tsKeys(), { + child: { logLevel: LogLevel.VERBOSE }, + endAt: { logLevel: LogLevel.VERBOSE }, + endBefore: { logLevel: LogLevel.VERBOSE }, + equalTo: { logLevel: LogLevel.VERBOSE }, + increment: { logLevel: LogLevel.VERBOSE }, + limitToFirst: { logLevel: LogLevel.VERBOSE }, + limitToLast: { logLevel: LogLevel.VERBOSE }, + orderByChild: { logLevel: LogLevel.VERBOSE }, + orderByKey: { logLevel: LogLevel.VERBOSE }, + orderByPriority: { logLevel: LogLevel.VERBOSE }, + orderByValue: { logLevel: LogLevel.VERBOSE }, + push: { logLevel: LogLevel.VERBOSE }, + query: { logLevel: LogLevel.VERBOSE }, + ref: { logLevel: LogLevel.VERBOSE }, + refFromURL: { logLevel: LogLevel.VERBOSE }, + remove: { logLevel: LogLevel.VERBOSE }, + serverTimestamp: null, + set: { logLevel: LogLevel.VERBOSE }, + setPriority: { logLevel: LogLevel.VERBOSE }, + setWithPriority: { logLevel: LogLevel.VERBOSE }, + startAfter: { logLevel: LogLevel.VERBOSE }, + startAt: { logLevel: LogLevel.VERBOSE }, + update: { logLevel: LogLevel.VERBOSE }, + }), + reexport('data-connect', 'firebase', 'firebase/data-connect', tsKeys(), { + mutationRef: { logLevel: LogLevel.VERBOSE }, + queryRef: { logLevel: LogLevel.VERBOSE }, + toQueryRef: { logLevel: LogLevel.VERBOSE }, + }), + reexport('firestore', 'rxfire', 'rxfire/firestore', tsKeys(), { + doc: { exportName: 'docSnapshots' }, + collection: { exportName: 'collectionSnapshots' }, + }), + reexport('firestore', 'firebase', 'firebase/firestore', tsKeys(), firestoreOverrides), + reexport('functions', 'rxfire', 'rxfire/functions', ["httpsCallable"], { + httpsCallable: { exportName: 'httpsCallableData' }, + }), + reexport('functions', 'firebase', 'firebase/functions', tsKeys()), + reexport('messaging', 'firebase', 'firebase/messaging', tsKeys(), { + isSupported: { blockUntilFirst: false }, + onMessage: { blockUntilFirst: false }, + deleteToken: { logLevel: LogLevel.VERBOSE }, + }), + reexport('remote-config', 'rxfire', 'rxfire/remote-config', tsKeys(), { + isSupported: { blockUntilFirst: false }, + getValue: { exportName: 'getValueChanges' }, + getString: { exportName: 'getStringChanges' }, + getNumber: { exportName: 'getNumberChanges' }, + getBoolean: { exportName: 'getBooleanChanges' }, + getAll: { exportName: 'getAllChanges' }, + }), + reexport('remote-config', 'firebase', 'firebase/remote-config', tsKeys()), + reexport('storage', 'rxfire', 'rxfire/storage', tsKeys(), { + getDownloadURL: null, + getMetadata: null, + uploadBytesResumable: null, + uploadString: null, + }), + reexport('storage', 'firebase', 'firebase/storage', tsKeys(), { + deleteObject: { logLevel: LogLevel.VERBOSE }, + ref: { logLevel: LogLevel.VERBOSE }, + updateMetadata: { logLevel: LogLevel.VERBOSE }, + }), + reexport('performance', 'rxfire', 'rxfire/performance', tsKeys(), { + getPerformance$: null, + trace: null, + }), + reexport('performance', 'firebase', 'firebase/performance', tsKeys(), { + trace: { logLevel: LogLevel.VERBOSE }, + }), + reexport('firestore/lite', 'rxfire', 'rxfire/firestore/lite', tsKeys(), { + doc: { exportName: 'docSnapshots' }, + collection: { exportName: 'collectionSnapshots' }, + }), + reexport('firestore/lite', 'firebase', 'firebase/firestore/lite', tsKeys(), firestoreOverrides), + ]); +} const src = (...args: string[]) => join(process.cwd(), 'src', ...args); const dest = (...args: string[]) => join(process.cwd(), 'dist', 'packages-dist', ...args); @@ -20,135 +263,147 @@ const rootPackage = import(join(process.cwd(), 'package.json')); async function replacePackageCoreVersion() { const root = await rootPackage; + // eslint-disable-next-line @typescript-eslint/no-var-requires const replace = require('replace-in-file'); + const files = dest('package.json'); return replace({ - files: dest('**', '*.js'), + files, from: 'ANGULARFIRE2_VERSION', to: root.version }); } -async function replacePackageJsonVersions() { - const path = dest('package.json'); - const root = await rootPackage; - const pkg = await import(path); - Object.keys(pkg.peerDependencies).forEach(peer => { - pkg.peerDependencies[peer] = root.dependencies[peer]; +async function writeVersionOverPlaceholder(root: { version: string }) { + const replace = require('replace-in-file'); + const replacements = await replace({ + // Glob patterns need forward slashes even on Windows. + files: `${dest('schematics').split(sep).join('/')}/**/*.js`, + from: /ANGULARFIRE2_VERSION/g, + to: root.version, + // Zero matches should reach the guard below, not replace-in-file's generic rejection. + allowEmptyPaths: true, }); - pkg.version = root.version; - return writeFile(path, JSON.stringify(pkg, null, 2)); + if (!replacements.some(replacement => replacement.hasChanged)) { + throw new Error('No ANGULARFIRE2_VERSION placeholder found in the compiled schematics — the version pin would ship disabled.'); + } } async function replaceSchematicVersions() { const root = await rootPackage; - const path = dest('schematics', 'versions.json'); - const dependencies = await import(path); - Object.keys(dependencies.default).forEach(name => { - dependencies.default[name].version = root.dependencies[name] || root.devDependencies[name]; + const packagesPath = dest('schematics', 'versions.json'); + const dependencies = await import(packagesPath); + // A missing version is dropped by JSON.stringify, so the package silently vanishes from the + // manifests the schematics generate. Fail the build instead. + const resolveVersion = (name: string): string => { + const version = root.dependencies[name] || root.devDependencies[name]; + if (!version) { + throw new Error(`No version resolved for ${name} in the root package.json, so the schematics would generate manifests without it.`); + } + return version; + }; + Object.keys(dependencies.peerDependencies).forEach(name => { + dependencies.peerDependencies[name].version = resolveVersion(name); }); - Object.keys(dependencies.firebaseFunctions).forEach(name => { - dependencies.firebaseFunctions[name].version = root.dependencies[name] || root.devDependencies[name]; + Object.keys(dependencies.firebaseFunctionsDependencies).forEach(name => { + dependencies.firebaseFunctionsDependencies[name].version = resolveVersion(name); }); - return writeFile(path, JSON.stringify(dependencies, null, 2)); + await writeVersionOverPlaceholder(root); + return writeFile(packagesPath, JSON.stringify(dependencies, null, 2)); } function spawnPromise(command: string, args: string[]) { - return new Promise(resolve => spawn(command, args, { stdio: 'inherit' }).on('close', resolve)); + return new Promise((resolve, reject) => spawn(command, args, { stdio: 'inherit' }).on('close', code => { + if (code === 0) { + resolve() + } else { + reject('Build failed.'); + } + }) + .on('error', reject)); } +// Path segments of each schematic entry point, relative to `schematics/` and without the file +// extension: esbuild compiles the `.ts` and loadCompiledSchematics requires the emitted `.js`. +const schematicEntryPoints = [ + ['update', 'index'], + ['deploy', 'actions'], + ['deploy', 'builder'], + ['add', 'index'], + ['setup', 'index'], + ['update', 'v7', 'index'], + ['update', 'v21', 'index'], +]; + async function compileSchematics() { - await spawnPromise(`npx`, ['tsc', '-p', src('schematics', 'tsconfig.json')]); - return Promise.all([ - copy(src('core', 'builders.json'), dest('builders.json')), - copy(src('core', 'collection.json'), dest('collection.json')), + await esbuild.build({ + entryPoints: schematicEntryPoints.map(segments => `${src('schematics', ...segments)}.ts`), + format: "cjs", + // turns out schematics don't support ESM, need to use webpack or shim these + // format: "esm", + // splitting: true, + // outExtension: {".js": ".mjs"}, + bundle: true, + minify: true, + platform: "node", + target: "es2016", + external: [ + "@angular-devkit/schematics", + "@angular-devkit/architect", + "@angular-devkit/core", + "rxjs", + "@schematics/angular", + "jsonc-parser", + "firebase-tools", + // The v21 migration parses user source with the TypeScript compiler; resolve it from + // the workspace at ng-update time instead of bundling ~3.5MB into the package. + "typescript" + ], + outdir: dest('schematics'), + }); + await Promise.all([ + copy(src('schematics', 'versions.json'), dest('schematics', 'versions.json')), + copy(src('schematics', 'builders.json'), dest('schematics', 'builders.json')), + copy(src('schematics', 'collection.json'), dest('schematics', 'collection.json')), + copy(src('schematics', 'migration.json'), dest('schematics', 'migration.json')), copy(src('schematics', 'deploy', 'schema.json'), dest('schematics', 'deploy', 'schema.json')), - replaceSchematicVersions() + copy(src('schematics', 'add', 'schema.json'), dest('schematics', 'add', 'schema.json')), + copy(src('schematics', 'setup', 'schema.json'), dest('schematics', 'setup', 'schema.json')), ]); + await replaceSchematicVersions(); + await loadCompiledSchematics(); } -async function measure(module: string) { - const path = dest('bundles', `${module}.umd.min.js`); - const file = await readFile(path); - const gzip = prettySize(gzipSync(file), true); - const size = prettySize(file.byteLength, true); - return { size, gzip }; +/** + * Loads every compiled schematic entry point, so a bundle that cannot even be required fails the + * build instead of shipping. + */ +async function loadCompiledSchematics() { + const failures: string[] = []; + for (const segments of schematicEntryPoints) { + try { + require(`${dest('schematics', ...segments)}.js`); + } catch (error) { + failures.push(` ${join(...segments)}.js: ${error}`); + } + } + if (failures.length) { + throw new Error(`Compiled schematics failed to load:\n${failures.join('\n')}`); + } } async function buildLibrary() { + await zoneWrapExports(); await spawnPromise('npx', ['ng', 'build']); await Promise.all([ copy(join(process.cwd(), '.npmignore'), dest('.npmignore')), copy(join(process.cwd(), 'README.md'), dest('README.md')), copy(join(process.cwd(), 'docs'), dest('docs')), compileSchematics(), - replacePackageJsonVersions(), - replacePackageCoreVersion() + replacePackageCoreVersion(), ]); } -function measureLibrary() { - return Promise.all(UMD_NAMES.map(measure)); -} - -async function buildDocs() { - // INVESTIGATE json to stdout rather than FS? - await Promise.all(MODULES.map(module => spawnPromise('npx', ['typedoc', `./src/${module}`, '--json', `./dist/typedocs/${module}.json`]))); - const entries = await Promise.all(MODULES.map(async (module) => { - const buffer = await readFile(`./dist/typedocs/${module}.json`); - const typedoc = JSON.parse(buffer.toString()); - // TODO infer the entryPoint from the package.json - const entryPoint = typedoc.children.find((c: any) => c.name === '"public_api"'); - const allChildren = [].concat(...typedoc.children.map(child => - // TODO chop out the working directory and filename - child.children ? child.children.map(c => ({ ...c, path: dirname(child.originalName.split(process.cwd())[1]) })) : [] - )); - return entryPoint.children - .filter(c => c.name[0] !== 'Éĩ' && c.name[0] !== '_' /* private */) - .map(child => ({ ...allChildren.find(c => child.target === c.id) })) - .reduce((acc, child) => ({ ...acc, [encodeURIComponent(child.name)]: child }), {}); - })); - const root = await rootPackage; - const pipes = ['MonoTypeOperatorFunction', 'OperatorFunction', 'AuthPipe', 'UnaryFunction']; - const tocType = child => { - const decorators: string[] = child.decorators && child.decorators.map(d => d.name) || []; - if (decorators.includes('NgModule')) { - return 'NgModule'; - } else if (child.kindString === 'Type alias') { - return 'Type alias'; - } else if (child.kindString === 'Variable' && child.defaultValue && child.defaultValue.startsWith('new InjectionToken')) { - return 'InjectionToken'; - } else if (child.type) { - return pipes.includes(child.type.name) ? 'Pipe' : child.type.name; - } else if (child.signatures && child.signatures[0] && child.signatures[0].type && pipes.includes(child.signatures[0].type.name)) { - return 'Pipe'; - } else { - return child.kindString; - } - }; - const tableOfContents = entries.reduce((acc, entry, index) => - ({ - ...acc, [MODULES[index]]: { - name: ENTRY_NAMES[index], - exports: Object.keys(entry).reduce((acc, key) => ({ ...acc, [key]: tocType(entry[key]) }), {}) - } - }), - {} - ); - const afdoc = entries.reduce((acc, entry, index) => ({ ...acc, [MODULES[index]]: entry }), { table_of_contents: tableOfContents }); - return writeFile(`./api-${root.version}.json`, JSON.stringify(afdoc, null, 2)); -} - -function packLibrary() { - return spawnPromise('npm', ['pack', './dist/packages-dist']); -} - -Promise.all([ - buildDocs(), - buildLibrary().then(packLibrary) -]).then(measureLibrary).then(stats => - console.log(` -Package Size Gzipped ------------------------------------- -${stats.map((s, i) => [MODULES[i].padEnd(16), s.size.padEnd(8), s.gzip].join('')).join('\n')}` - ) -); +buildLibrary().catch(err => { + console.error(err); + process.exit(1); +}) diff --git a/tools/jasmine.js b/tools/jasmine.js deleted file mode 100644 index dd9650197..000000000 --- a/tools/jasmine.js +++ /dev/null @@ -1,30 +0,0 @@ -Jasmine = require('jasmine'); - -jasmine = new Jasmine(); -jasmine.loadConfig({ - spec_dir: '.', - spec_files: [ - 'dist/out-tsc/jasmine/**/*.jasmine.js', - 'dist/out-tsc/jasmine/**/*.spec.js' - ] -}); - -require('reflect-metadata'); -require('zone.js/dist/zone-node'); -require('zone.js/dist/async-test'); -require('zone.js/dist/sync-test'); -require('zone.js/dist/fake-async-test'); -require('zone.js/dist/proxy'); -require('zone.js/dist/zone-patch-rxjs'); -require('zone.js/dist/task-tracking'); - -const { getTestBed } = require('@angular/core/testing'); -const { platformServerTesting, ServerTestingModule } = require('@angular/platform-server/testing'); - -// First, initialize the Angular testing environment. -getTestBed().initTestEnvironment( - ServerTestingModule, - platformServerTesting() -); - -jasmine.execute(); \ No newline at end of file diff --git a/tools/jasmine.ts b/tools/jasmine.ts new file mode 100644 index 000000000..721f9106b --- /dev/null +++ b/tools/jasmine.ts @@ -0,0 +1,25 @@ +// @ts-ignore +import Jasmine from 'jasmine'; + +import 'reflect-metadata'; +import 'zone.js'; + +import { getTestBed } from '@angular/core/testing'; +import { platformServerTesting, ServerTestingModule } from '@angular/platform-server/testing'; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + ServerTestingModule, + platformServerTesting() +); + +const tests = new Jasmine({}); +tests.loadConfig({ + spec_dir: '.', + spec_files: [ + 'dist/out-tsc/jasmine/**/*.jasmine.js', + 'dist/out-tsc/jasmine/**/*.spec.js', + ] +}); + +tests.execute(); diff --git a/tools/release.sh b/tools/release.sh deleted file mode 100755 index c5ba8c95a..000000000 --- a/tools/release.sh +++ /dev/null @@ -1,17 +0,0 @@ -echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc -cd dist/packages-dist - -PRODUCTION_TEST="^[^-]*$" - -if test $TAG_NAME; then - if [[ $TAG_NAME =~ $PRODUCTION_TEST ]]; then - npm publish . - else - npm publish . --tag next - fi -else - npm publish . --tag canary -fi - -# TODO put this in a shell trap -rm -f .npmrc \ No newline at end of file diff --git a/tools/run-typings-test.js b/tools/run-typings-test.js deleted file mode 100644 index a9f43b035..000000000 --- a/tools/run-typings-test.js +++ /dev/null @@ -1,77 +0,0 @@ -/** - * This file uses the test project at test/typings-test to validate - * that AngularFire typings don't produce errors in user code. - * - * 1. Create a temp directory to copy the test project - * 2. Create a package.json based on test-project/package.sample.json, - * using versions from the project's root package.json. - * 3. NPM install inside the temporary project directory - * 4. Run TSC against the project's local tsconfig.json and local node_modules - * 5. - */ -const fs = require('fs'); -const path = require('path'); -const ncp = require('ncp'); -const rimraf = require('rimraf'); -const child_process = require('child_process'); -const pathToTestSrcFolder = path.resolve(__dirname, '../test/typings-test/'); -const binaryPath = path.resolve(__dirname, '../node_modules/.bin') -const rootPackage = require(path.resolve(__dirname, '../package.json')); - -const startingCwd = process.cwd(); - -const pathToTestFolder = fs.mkdtempSync('/tmp/typings-test-'); - -process.chdir(pathToTestFolder) - -ncp(pathToTestSrcFolder, pathToTestFolder, () => { - const samplePackage = require(`${pathToTestFolder}/package.sample.json`); - - fs.writeFileSync(`${pathToTestFolder}/package.json`, JSON.stringify(samplePackage, null, 2) - .replace('{{ANGULARFIRE_VERSION}}', path.resolve(__dirname, '../dist/packages-dist')) - .replace('{{FIREBASE_VERSION}}', rootPackage.dependencies['firebase']) - .replace('{{RXJS_VERSION}}', rootPackage.dependencies.rxjs) - .replace('{{ZONE_VERSION}}', rootPackage.dependencies['zone.js']) - .replace('{{TYPESCRIPT_VERSION}}', rootPackage.devDependencies.typescript) - .replace(/\{\{ANGULAR_VERSION\}\}/g, rootPackage.dependencies['@angular/core'])); - - spawnIt('yarn', ['install']) - .then(_ => spawnIt(`${pathToTestFolder}/node_modules/.bin/tsc`, ['--version'])) - .then(_ => new Promise((res, rej) => { - child_process.exec(`${pathToTestFolder}/node_modules/.bin/tsc --diagnostics -p ${pathToTestFolder}/tsconfig-test.json`, (err, stdout, stderr) => { - console.log(stdout); - if (err) { - rej(1); - } else { - res(); - } - }); - })) - .catch(_ => { - // resolve with exit code 1 - return Promise.resolve(1) - }) - .then(cleanup) - .then(code => process.exit(code || 0)); -}) - -function spawnIt(program, args) { - console.log('-----------------------------------'); - console.log(program, (args && args.join(' ')) || ''); - console.log('-----------------------------------'); - return new Promise((res, rej) => { - child_process.spawn(program, args, { - cwd: pathToTestFolder, - stdio: 'inherit' - }) - .on('close', (code) => { - if (code) return rej(code); - res(); - }) - }); -} - -function cleanup (val) { - rimraf.sync(pathToTestFolder); - return val; -} diff --git a/tools/test.sh b/tools/test.sh deleted file mode 100755 index 2678d1cb8..000000000 --- a/tools/test.sh +++ /dev/null @@ -1,7 +0,0 @@ -wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - -echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list - -apt-get -y update -apt-get -y install google-chrome-stable - -yarn test:all diff --git a/tsconfig.base.json b/tsconfig.base.json index 827a694b4..af0766384 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -9,31 +9,21 @@ "downlevelIteration": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, - "module": "esnext", - "moduleResolution": "node", + "module": "preserve", + "moduleResolution": "bundler", "importHelpers": true, - "target": "es2015", + "target": "es2020", + "skipLibCheck": true, "typeRoots": [ "node_modules/@types" ], "lib": [ - "es2018", + "es2022", "dom" ], "paths": { "@angular/fire": ["dist/packages-dist"], - "@angular/fire/analytics": ["dist/packages-dist/analytics"], - "@angular/fire/auth": ["dist/packages-dist/auth"], - "@angular/fire/auth-guard": ["dist/packages-dist/auth-guard"], - "@angular/fire/database": ["dist/packages-dist/database"], - "@angular/fire/database-deprecated": ["dist/packages-dist/database-deprecated"], - "@angular/fire/firebase-node": ["dist/packages-dist/firebase-node"], - "@angular/fire/firestore": ["dist/packages-dist/firestore"], - "@angular/fire/functions": ["dist/packages-dist/functions"], - "@angular/fire/messaging": ["dist/packages-dist/messaging"], - "@angular/fire/performance": ["dist/packages-dist/performance"], - "@angular/fire/remote-config": ["dist/packages-dist/remote-config"], - "@angular/fire/storage": ["dist/packages-dist/storage"] + "@angular/fire/*": ["dist/packages-dist/*"] } } - } \ No newline at end of file + } diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 000000000..66c6016a6 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,16 @@ +{ + "files": [ + "tools/build.ts" + ], + "compilerOptions": { + "outDir": "tools", + "skipLibCheck": true, + "lib": ["es2022", "dom"], + "module": "commonjs", + "target": "ES2020", + "plugins": [ + { "transform": "ts-transformer-keys/transformer" } + ], + "baseUrl": "./" + } +} \ No newline at end of file diff --git a/tsconfig.jasmine.json b/tsconfig.jasmine.json index 531e06287..3301fbf20 100644 --- a/tsconfig.jasmine.json +++ b/tsconfig.jasmine.json @@ -2,19 +2,22 @@ "extends": "./tsconfig.base.json", "compilerOptions": { "outDir": "dist/out-tsc/jasmine", - "module": "commonjs", + "module": "preserve", "target": "es2015", "allowJs": true, "resolveJsonModule": true, + "esModuleInterop": true, "types": [ "jasmine", "node" ] }, "include": [ + "tools/jasmine.ts", "src/**/*.jasmine.ts", - "src/**/*.spec.ts", + // Not sure what is wrong here, but since upgrading karma it's fallen apart + // "src/**/*.spec.ts", "src/**/*.d.ts", - "node_modules/zone.js/dist/zone.js.d.ts" + "node_modules/zone.js/zone.d.ts" ] } diff --git a/tsconfig.json b/tsconfig.json index 6427efaf0..4f64a5584 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,31 +1,24 @@ { - "extends": "./tsconfig.base.json", - "compilerOptions": { - "outDir": "dist/out-tsc/lib", - "target": "es2015", - "declaration": true, - "inlineSources": true, - "types": [ - "node", - "zone.js" - ], - "lib": [ - "dom", - "es2018" - ] - }, - "angularCompilerOptions": { - "enableIvy": false, - "annotateForClosureCompiler": true, - "skipTemplateCodegen": true, - "strictMetadataEmit": true, - "fullTemplateTypeCheck": true, - "strictInjectionParameters": true, - "enableResourceInlining": true - }, - "exclude": [ - "src/test.ts", - "src/**/*.spec.ts" + "extends": "./tsconfig.base.json", + "compilerOptions": { + "outDir": "dist/out-tsc/lib", + "target": "es2015", + "declaration": true, + "inlineSources": true, + "declarationMap": false, + "types": [ + "node" + ], + "lib": [ + "dom", + "es2022" ] - } - \ No newline at end of file + }, + "angularCompilerOptions": { + "compilationMode": "partial" + }, + "exclude": [ + "src/test.ts", + "src/**/*.spec.ts" + ] +} diff --git a/tsconfig.spec.json b/tsconfig.spec.json index e7a72f174..e4e1bff9f 100644 --- a/tsconfig.spec.json +++ b/tsconfig.spec.json @@ -2,6 +2,7 @@ "extends": "./tsconfig.base.json", "compilerOptions": { "outDir": "dist/out-tsc/spec", + "module": "preserve", "types": [ "jasmine", "node" @@ -11,9 +12,9 @@ "src/test.ts" ], "include": [ - "src/root.spec.ts", + "src/**/*.spec.ts", "src/**/*.d.ts", - "node_modules/zone.js/dist/zone.js.d.ts" + "node_modules/zone.js/zone.d.ts" ] } \ No newline at end of file diff --git a/tslint.json b/tslint.json deleted file mode 100644 index 60559a052..000000000 --- a/tslint.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "extends": "tslint:recommended", - "rules": { - "array-type": false, - "arrow-parens": false, - "deprecation": { - "severity": "warn" - }, - "component-class-suffix": true, - "contextual-lifecycle": true, - "directive-class-suffix": true, - "no-shadowed-variable": false, - "directive-selector": [ - true, - "attribute", - "app", - "camelCase" - ], - "component-selector": [ - true, - "element", - "app", - "kebab-case" - ], - "import-blacklist": [ - true, - "rxjs/Rx" - ], - "interface-name": false, - "max-classes-per-file": false, - "max-line-length": [ - true, - 140 - ], - "member-access": false, - "member-ordering": [ - true, - { - "order": [ - "static-field", - "instance-field", - "static-method", - "instance-method" - ] - } - ], - "no-consecutive-blank-lines": false, - "no-console": [ - true, - "debug", - "info", - "time", - "timeEnd", - "trace" - ], - "no-empty": false, - "no-inferrable-types": [ - true, - "ignore-params" - ], - "no-non-null-assertion": true, - "no-redundant-jsdoc": true, - "no-switch-case-fall-through": true, - "no-var-requires": false, - "object-literal-key-quotes": [ - true, - "as-needed" - ], - "object-literal-sort-keys": false, - "ordered-imports": false, - "quotemark": [ - true, - "single" - ], - "trailing-comma": false, - "no-conflicting-lifecycle": true, - "no-host-metadata-property": true, - "no-input-rename": true, - "no-inputs-metadata-property": true, - "no-output-native": true, - "no-output-on-prefix": true, - "no-output-rename": true, - "no-outputs-metadata-property": true, - "template-banana-in-box": true, - "template-no-negated-async": true, - "use-lifecycle-interface": true, - "use-pipe-transform-interface": true, - "variable-name": [ - true, - "ban-keywords", - "check-format", - "allow-leading-underscore" - ] - }, - "rulesDirectory": [ - "codelyzer" - ] - } diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index 5df65db16..000000000 --- a/yarn.lock +++ /dev/null @@ -1,14662 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@angular-devkit/architect@0.901.9", "@angular-devkit/architect@>= 0.900 < 0.1100": - version "0.901.9" - resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.901.9.tgz#5d849a120449b2f91ec6eef44ddad74ffb7ad810" - integrity sha512-Xokyh7bv4qICHpb5Xui1jPTi6ZZvzR5tbTIxT0DFWqw16TEkFgkNubQsW6mFSR3g3CXdySMfOwWExfa/rE1ggA== - dependencies: - "@angular-devkit/core" "9.1.9" - rxjs "6.5.4" - -"@angular-devkit/build-angular@>= 0.900 < 0.1100": - version "0.901.9" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-0.901.9.tgz#a60c41497e69d663cf2c74778cc72dd262cc09a1" - integrity sha512-eC6iZQR5tr9dz/SkR3/3Y8Fau/2IzEfHlFCf2mqsOLkbc0MWyM/3RcuZQhRGdVOyzDCIbfzJGY0N3ejkEn2EUg== - dependencies: - "@angular-devkit/architect" "0.901.9" - "@angular-devkit/build-optimizer" "0.901.9" - "@angular-devkit/build-webpack" "0.901.9" - "@angular-devkit/core" "9.1.9" - "@babel/core" "7.9.0" - "@babel/generator" "7.9.3" - "@babel/preset-env" "7.9.0" - "@babel/template" "7.8.6" - "@jsdevtools/coverage-istanbul-loader" "3.0.3" - "@ngtools/webpack" "9.1.9" - ajv "6.12.0" - autoprefixer "9.7.4" - babel-loader "8.0.6" - browserslist "^4.9.1" - cacache "15.0.0" - caniuse-lite "^1.0.30001032" - circular-dependency-plugin "5.2.0" - copy-webpack-plugin "6.0.2" - core-js "3.6.4" - css-loader "3.5.1" - cssnano "4.1.10" - file-loader "6.0.0" - find-cache-dir "3.3.1" - glob "7.1.6" - jest-worker "25.1.0" - karma-source-map-support "1.4.0" - less "3.11.3" - less-loader "5.0.0" - license-webpack-plugin "2.1.4" - loader-utils "2.0.0" - mini-css-extract-plugin "0.9.0" - minimatch "3.0.4" - open "7.0.3" - parse5 "4.0.0" - postcss "7.0.27" - postcss-import "12.0.1" - postcss-loader "3.0.0" - raw-loader "4.0.0" - regenerator-runtime "0.13.5" - rimraf "3.0.2" - rollup "2.1.0" - rxjs "6.5.4" - sass "1.26.3" - sass-loader "8.0.2" - semver "7.1.3" - source-map "0.7.3" - source-map-loader "0.2.4" - speed-measure-webpack-plugin "1.3.1" - style-loader "1.1.3" - stylus "0.54.7" - stylus-loader "3.0.2" - terser "4.6.10" - terser-webpack-plugin "3.0.3" - tree-kill "1.2.2" - webpack "4.42.0" - webpack-dev-middleware "3.7.2" - webpack-dev-server "3.11.0" - webpack-merge "4.2.2" - webpack-sources "1.4.3" - webpack-subresource-integrity "1.4.0" - worker-plugin "4.0.3" - -"@angular-devkit/build-ng-packagr@>= 0.900 < 0.1100": - version "0.901.9" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-ng-packagr/-/build-ng-packagr-0.901.9.tgz#ade174940ab11da292a773a2b8149f9c6f5e78c3" - integrity sha512-tZvRAJcBSvYEPcwRRdXtpklS/7pf5Y5QvsTuK34MiVZD7T/fBwMtIqGhIq3o0uxVqBnQdpBFxoRgE3DL1AdY7g== - dependencies: - "@angular-devkit/architect" "0.901.9" - rxjs "6.5.4" - -"@angular-devkit/build-optimizer@0.901.9": - version "0.901.9" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.901.9.tgz#c8018de2406c8bbf32bf74cdacd6fa1df384a8d0" - integrity sha512-AcDhE7RHmaVEaDB02MHp1PR2gdUg3+G/12pDC3GeAlfP1GD/sVBpcqPL6DHFp0dMm/FsvSfVSaXpzD7jZBeIKQ== - dependencies: - loader-utils "2.0.0" - source-map "0.7.3" - tslib "1.11.1" - typescript "3.6.5" - webpack-sources "1.4.3" - -"@angular-devkit/build-webpack@0.901.9": - version "0.901.9" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.901.9.tgz#19e783950b2f22ceaa0c33b07552bd225dcd412b" - integrity sha512-Bha9LruitixhtJm72FGzqfDfgsOsrMT3EbNSql2muyoELIYbLDOvNZjcDD06CPcOAWSg6/tH9caOTFS2Zj9yOw== - dependencies: - "@angular-devkit/architect" "0.901.9" - "@angular-devkit/core" "9.1.9" - rxjs "6.5.4" - -"@angular-devkit/core@8.3.24", "@angular-devkit/core@^8.3.21": - version "8.3.24" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-8.3.24.tgz#94e8e6470443e2752c319cb95cdbeae9e37e19fe" - integrity sha512-xpT5yg+ddGDnifryBv2sRSYtq5F3iZIS+lN/K2AhhEa50B7Z+QaCVlEzoV/IfrGd6sLArdnKYwjLHFZ0LElUuw== - dependencies: - ajv "6.10.2" - fast-json-stable-stringify "2.0.0" - magic-string "0.25.3" - rxjs "6.4.0" - source-map "0.7.3" - -"@angular-devkit/core@9.1.9", "@angular-devkit/core@^9.0.0 || ^10.0.0": - version "9.1.9" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-9.1.9.tgz#e6283912093179313ccfc69856841e9b6b318231" - integrity sha512-SWgBh4an/Vezjw2BZ5S+bKvuK5lH6gOtR8d5YjN9vxpJSZ0GimrGjfnLlWOkwWAsU8jfn4JzofECUHwX/7EW6Q== - dependencies: - ajv "6.12.0" - fast-json-stable-stringify "2.1.0" - magic-string "0.25.7" - rxjs "6.5.4" - source-map "0.7.3" - -"@angular-devkit/schematics@8.3.24", "@angular-devkit/schematics@^8.3.21": - version "8.3.24" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-8.3.24.tgz#583b1841d032eccf31cc86dbd15c5ac15a50cf95" - integrity sha512-HrwDCgw7i3GrNns0Ce5zStWkxBqlcLuDkMcLY6981jpvVzgXMIQ+YqDrJ2kD46xHh979ev7hhw1d6jwPXh85Xw== - dependencies: - "@angular-devkit/core" "8.3.24" - rxjs "6.4.0" - -"@angular-devkit/schematics@9.1.9", "@angular-devkit/schematics@^9.0.0 || ^10.0.0": - version "9.1.9" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-9.1.9.tgz#1369ee586cb91ffebf12673bb839d31ad5c00357" - integrity sha512-aKuMmS3wshOTl9+01jiB50ml09fRN1WfOOtoNqwvKTEi87DrT6Mn3l0eVQo8PJK/bIq/FBmPgsIl2nsETiBSxg== - dependencies: - "@angular-devkit/core" "9.1.9" - ora "4.0.3" - rxjs "6.5.4" - -"@angular/animations@ ^9.0.0 || ^10.0.0": - version "9.1.11" - resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-9.1.11.tgz#2c7b6e584df0ba0884d05f01fa7ab86c1fdd1c5e" - integrity sha512-VKAExUnEJfo1PDQKagpx2pn+QMZCsPLRiADzTdl4U0VPylK3ALbn4ZNY9UbdwyE2plitz++LkH7sEGGfh+PNrQ== - -"@angular/cli@^9.0.0 || ^10.0.0": - version "9.1.9" - resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-9.1.9.tgz#e9caced7c107c65075fe1b4c42a4712a5aeb992c" - integrity sha512-k8C0OY3oHoixd3buCgF8+VFe8YZGSGiprnbVMEF2WJHUUw87lPCu/d7dbID3AtVwdKdAB275rAt6IZEIzXInbw== - dependencies: - "@angular-devkit/architect" "0.901.9" - "@angular-devkit/core" "9.1.9" - "@angular-devkit/schematics" "9.1.9" - "@schematics/angular" "9.1.9" - "@schematics/update" "0.901.9" - "@yarnpkg/lockfile" "1.1.0" - ansi-colors "4.1.1" - debug "4.1.1" - ini "1.3.5" - inquirer "7.1.0" - npm-package-arg "8.0.1" - npm-pick-manifest "6.0.0" - open "7.0.3" - pacote "9.5.12" - read-package-tree "5.3.1" - rimraf "3.0.2" - semver "7.1.3" - symbol-observable "1.2.0" - universal-analytics "0.4.20" - uuid "7.0.2" - -"@angular/common@^9.0.0 || ^10.0.0": - version "9.1.11" - resolved "https://registry.yarnpkg.com/@angular/common/-/common-9.1.11.tgz#1323f7b043410791bd2d0d71b0bbb1f862319c04" - integrity sha512-Vh5lF7zWwDK9RedmYXUc8vUXyrecR3j1mAWlTlnmcHYxxFThPzN/dr0slQcPi6nyJn0EmyRKUGvAoZx4rIb7wg== - -"@angular/compiler-cli@^9.0.0 || ^10.0.0": - version "9.1.11" - resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-9.1.11.tgz#39da68ddadb52008fe5231141707bddd3aa790b2" - integrity sha512-9qIxbtpRXOQnRm6inxCa5HuH87MSuMzuceD0YBVzl8v+vLtewon9KXYMmF4kTBhWa/LEa8FrajljLh0azf3VLg== - dependencies: - canonical-path "1.0.0" - chokidar "^3.0.0" - convert-source-map "^1.5.1" - dependency-graph "^0.7.2" - fs-extra "4.0.2" - magic-string "^0.25.0" - minimist "^1.2.0" - reflect-metadata "^0.1.2" - semver "^6.3.0" - source-map "^0.6.1" - sourcemap-codec "^1.4.8" - yargs "15.3.0" - -"@angular/compiler@^9.0.0 || ^10.0.0": - version "9.1.11" - resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-9.1.11.tgz#4c7100f53c87f47e793e149427b8bdee44302381" - integrity sha512-MbVheCG0U8gt6xtiipau20N26mD2sXjLChVmRKgO6rbDruxboNMZfEd94q9NP9JRaUsVnjXvY7GMDldoymdXig== - -"@angular/core@^9.0.0 || ^10.0.0": - version "9.1.11" - resolved "https://registry.yarnpkg.com/@angular/core/-/core-9.1.11.tgz#7a92d27292212ed381be15f9000d4019867f1c7c" - integrity sha512-KAlEedBo761O1aeoTJVziOSHi8Fttk9ipvbDZXYT/o0W/KdVwubxP34g9t5aD8LCcF8+L0z4VLw++HjdJAUpwg== - -"@angular/platform-browser-dynamic@^9.0.0 || ^10.0.0": - version "9.1.11" - resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-9.1.11.tgz#82af336b05e0d7b7478a2ca7f6282825b211f340" - integrity sha512-Qw3rfVFF0Wtu+UwraqKPCgTA3uoNPGf4vKSfuCuXTrG0p7j+3mCP59aUv5gGH7GV1UQ++jZRx5pbWF43zrC8Hw== - -"@angular/platform-browser@^9.0.0 || ^10.0.0": - version "9.1.11" - resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-9.1.11.tgz#4da8e2a4231d5162304746a39f54cb2d3f241b7c" - integrity sha512-KDxoiFhW4UD+EqchcKpQVSLwg9Rd3JbWytZLchFV6nH8BFnshfJtw2tyPT8bMhFVG9n9zSR4QSGaozWgoDs9mw== - -"@angular/platform-server@^9.0.0 || ^10.0.0": - version "9.1.11" - resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-9.1.11.tgz#c47144568fdafee4eae8f48a686568f19fc2c23b" - integrity sha512-eHqlbtDHvYZIxtY6iKYpuRg9vTw5JoEH1M5rna2YDPI1MOnDdNhmpupIF6M3jnNAzN6xsh4X4wxW3PYHJVJviA== - dependencies: - domino "^2.1.2" - xhr2 "^0.2.0" - -"@angular/router@^9.0.0 || ^10.0.0": - version "9.1.11" - resolved "https://registry.yarnpkg.com/@angular/router/-/router-9.1.11.tgz#b6d28af55fe5631bbc46f306a0e7866253d4f3b1" - integrity sha512-D6CCDeSK/F6dWSB/a1g/zB072xG5LadLSV8afQ57oX1KHePx21LcoRG4tUtFMMHh/jZXRc9pMQIR1/9FrrXF3Q== - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" - integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== - dependencies: - "@babel/highlight" "^7.8.3" - -"@babel/compat-data@^7.8.6", "@babel/compat-data@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.9.0.tgz#04815556fc90b0c174abd2c0c1bb966faa036a6c" - integrity sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g== - dependencies: - browserslist "^4.9.1" - invariant "^2.2.4" - semver "^5.5.0" - -"@babel/core@7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e" - integrity sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w== - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.9.0" - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helpers" "^7.9.0" - "@babel/parser" "^7.9.0" - "@babel/template" "^7.8.6" - "@babel/traverse" "^7.9.0" - "@babel/types" "^7.9.0" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.1" - json5 "^2.1.2" - lodash "^4.17.13" - resolve "^1.3.2" - semver "^5.4.1" - source-map "^0.5.0" - -"@babel/core@^7.7.5": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.8.4.tgz#d496799e5c12195b3602d0fddd77294e3e38e80e" - integrity sha512-0LiLrB2PwrVI+a2/IEskBopDYSd8BCb3rOvH7D5tzoWd696TBEduBvuLVm4Nx6rltrLZqvI3MCalB2K2aVzQjA== - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.8.4" - "@babel/helpers" "^7.8.4" - "@babel/parser" "^7.8.4" - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.8.4" - "@babel/types" "^7.8.3" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.1" - json5 "^2.1.0" - lodash "^4.17.13" - resolve "^1.3.2" - semver "^5.4.1" - source-map "^0.5.0" - -"@babel/generator@7.9.3": - version "7.9.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.3.tgz#7c8b2956c6f68b3ab732bd16305916fbba521d94" - integrity sha512-RpxM252EYsz9qLUIq6F7YJyK1sv0wWDBFuztfDGWaQKzHjqDHysxSiRUpA/X9jmfqo+WzkAVKFaUily5h+gDCQ== - dependencies: - "@babel/types" "^7.9.0" - jsesc "^2.5.1" - lodash "^4.17.13" - source-map "^0.5.0" - -"@babel/generator@^7.4.0", "@babel/generator@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.4.tgz#35bbc74486956fe4251829f9f6c48330e8d0985e" - integrity sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA== - dependencies: - "@babel/types" "^7.8.3" - jsesc "^2.5.1" - lodash "^4.17.13" - source-map "^0.5.0" - -"@babel/generator@^7.9.0": - version "7.9.4" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.4.tgz#12441e90c3b3c4159cdecf312075bf1a8ce2dbce" - integrity sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA== - dependencies: - "@babel/types" "^7.9.0" - jsesc "^2.5.1" - lodash "^4.17.13" - source-map "^0.5.0" - -"@babel/helper-annotate-as-pure@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz#60bc0bc657f63a0924ff9a4b4a0b24a13cf4deee" - integrity sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw== - dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-builder-binary-assignment-operator-visitor@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz#c84097a427a061ac56a1c30ebf54b7b22d241503" - integrity sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw== - dependencies: - "@babel/helper-explode-assignable-expression" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/helper-compilation-targets@^7.8.7": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz#dac1eea159c0e4bd46e309b5a1b04a66b53c1dde" - integrity sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw== - dependencies: - "@babel/compat-data" "^7.8.6" - browserslist "^4.9.1" - invariant "^2.2.4" - levenary "^1.1.1" - semver "^5.5.0" - -"@babel/helper-create-regexp-features-plugin@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.3.tgz#c774268c95ec07ee92476a3862b75cc2839beb79" - integrity sha512-Gcsm1OHCUr9o9TcJln57xhWHtdXbA2pgQ58S0Lxlks0WMGNXuki4+GLfX0p+L2ZkINUGZvfkz8rzoqJQSthI+Q== - dependencies: - "@babel/helper-regex" "^7.8.3" - regexpu-core "^4.6.0" - -"@babel/helper-create-regexp-features-plugin@^7.8.8": - version "7.8.8" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz#5d84180b588f560b7864efaeea89243e58312087" - integrity sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-regex" "^7.8.3" - regexpu-core "^4.7.0" - -"@babel/helper-define-map@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz#a0655cad5451c3760b726eba875f1cd8faa02c15" - integrity sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g== - dependencies: - "@babel/helper-function-name" "^7.8.3" - "@babel/types" "^7.8.3" - lodash "^4.17.13" - -"@babel/helper-explode-assignable-expression@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz#a728dc5b4e89e30fc2dfc7d04fa28a930653f982" - integrity sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw== - dependencies: - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/helper-function-name@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca" - integrity sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA== - dependencies: - "@babel/helper-get-function-arity" "^7.8.3" - "@babel/template" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/helper-get-function-arity@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5" - integrity sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA== - dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-hoist-variables@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz#1dbe9b6b55d78c9b4183fc8cdc6e30ceb83b7134" - integrity sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg== - dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-member-expression-to-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz#659b710498ea6c1d9907e0c73f206eee7dadc24c" - integrity sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA== - dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-module-imports@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz#7fe39589b39c016331b6b8c3f441e8f0b1419498" - integrity sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg== - dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-module-transforms@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz#43b34dfe15961918707d247327431388e9fe96e5" - integrity sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA== - dependencies: - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.6" - "@babel/helper-simple-access" "^7.8.3" - "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/template" "^7.8.6" - "@babel/types" "^7.9.0" - lodash "^4.17.13" - -"@babel/helper-optimise-call-expression@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9" - integrity sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ== - dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" - integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ== - -"@babel/helper-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.8.3.tgz#139772607d51b93f23effe72105b319d2a4c6965" - integrity sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ== - dependencies: - lodash "^4.17.13" - -"@babel/helper-remap-async-to-generator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz#273c600d8b9bf5006142c1e35887d555c12edd86" - integrity sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-wrap-function" "^7.8.3" - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/helper-replace-supers@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.3.tgz#91192d25f6abbcd41da8a989d4492574fb1530bc" - integrity sha512-xOUssL6ho41U81etpLoT2RTdvdus4VfHamCuAm4AHxGr+0it5fnwoVdwUJ7GFEqCsQYzJUhcbsN9wB9apcYKFA== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.8.3" - "@babel/helper-optimise-call-expression" "^7.8.3" - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/helper-replace-supers@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8" - integrity sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.8.3" - "@babel/helper-optimise-call-expression" "^7.8.3" - "@babel/traverse" "^7.8.6" - "@babel/types" "^7.8.6" - -"@babel/helper-simple-access@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz#7f8109928b4dab4654076986af575231deb639ae" - integrity sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw== - dependencies: - "@babel/template" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/helper-split-export-declaration@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" - integrity sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA== - dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-validator-identifier@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz#ad53562a7fc29b3b9a91bbf7d10397fd146346ed" - integrity sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw== - -"@babel/helper-wrap-function@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz#9dbdb2bb55ef14aaa01fe8c99b629bd5352d8610" - integrity sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ== - dependencies: - "@babel/helper-function-name" "^7.8.3" - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/helpers@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.8.4.tgz#754eb3ee727c165e0a240d6c207de7c455f36f73" - integrity sha512-VPbe7wcQ4chu4TDQjimHv/5tj73qz88o12EPkO2ValS2QiQS/1F2SsjyIGNnAD0vF/nZS6Cf9i+vW6HIlnaR8w== - dependencies: - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.8.4" - "@babel/types" "^7.8.3" - -"@babel/helpers@^7.9.0": - version "7.9.2" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.2.tgz#b42a81a811f1e7313b88cba8adc66b3d9ae6c09f" - integrity sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA== - dependencies: - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.9.0" - "@babel/types" "^7.9.0" - -"@babel/highlight@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797" - integrity sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg== - dependencies: - chalk "^2.0.0" - esutils "^2.0.2" - js-tokens "^4.0.0" - -"@babel/parser@^7.4.3", "@babel/parser@^7.7.5", "@babel/parser@^7.8.3", "@babel/parser@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.4.tgz#d1dbe64691d60358a974295fa53da074dd2ce8e8" - integrity sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw== - -"@babel/parser@^7.8.6", "@babel/parser@^7.9.0": - version "7.9.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8" - integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA== - -"@babel/plugin-proposal-async-generator-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz#bad329c670b382589721b27540c7d288601c6e6f" - integrity sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-remap-async-to-generator" "^7.8.3" - "@babel/plugin-syntax-async-generators" "^7.8.0" - -"@babel/plugin-proposal-dynamic-import@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz#38c4fe555744826e97e2ae930b0fb4cc07e66054" - integrity sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-dynamic-import" "^7.8.0" - -"@babel/plugin-proposal-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz#da5216b238a98b58a1e05d6852104b10f9a70d6b" - integrity sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-json-strings" "^7.8.0" - -"@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz#e4572253fdeed65cddeecfdab3f928afeb2fd5d2" - integrity sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" - -"@babel/plugin-proposal-numeric-separator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz#5d6769409699ec9b3b68684cd8116cedff93bad8" - integrity sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.8.3" - -"@babel/plugin-proposal-object-rest-spread@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.0.tgz#a28993699fc13df165995362693962ba6b061d6f" - integrity sha512-UgqBv6bjq4fDb8uku9f+wcm1J7YxJ5nT7WO/jBr0cl0PLKb7t1O6RNR1kZbjgx2LQtsDI9hwoQVmn0yhXeQyow== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - -"@babel/plugin-proposal-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz#9dee96ab1650eed88646ae9734ca167ac4a9c5c9" - integrity sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" - -"@babel/plugin-proposal-optional-chaining@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz#31db16b154c39d6b8a645292472b98394c292a58" - integrity sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.0" - -"@babel/plugin-proposal-unicode-property-regex@^7.4.4", "@babel/plugin-proposal-unicode-property-regex@^7.8.3": - version "7.8.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz#ee3a95e90cdc04fe8cd92ec3279fa017d68a0d1d" - integrity sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.8.8" - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-syntax-async-generators@^7.8.0": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-dynamic-import@^7.8.0": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" - integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-json-strings@^7.8.0": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.8.0", "@babel/plugin-syntax-numeric-separator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz#0e3fb63e09bea1b11e96467271c8308007e7c41f" - integrity sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-syntax-object-rest-spread@^7.8.0": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.0": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.0": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-top-level-await@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz#3acdece695e6b13aaf57fc291d1a800950c71391" - integrity sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-arrow-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz#82776c2ed0cd9e1a49956daeb896024c9473b8b6" - integrity sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-async-to-generator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz#4308fad0d9409d71eafb9b1a6ee35f9d64b64086" - integrity sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ== - dependencies: - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-remap-async-to-generator" "^7.8.3" - -"@babel/plugin-transform-block-scoped-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz#437eec5b799b5852072084b3ae5ef66e8349e8a3" - integrity sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-block-scoping@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz#97d35dab66857a437c166358b91d09050c868f3a" - integrity sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - lodash "^4.17.13" - -"@babel/plugin-transform-classes@^7.9.0": - version "7.9.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.2.tgz#8603fc3cc449e31fdbdbc257f67717536a11af8d" - integrity sha512-TC2p3bPzsfvSsqBZo0kJnuelnoK9O3welkUpqSqBQuBF6R5MN2rysopri8kNvtlGIb2jmUO7i15IooAZJjZuMQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-define-map" "^7.8.3" - "@babel/helper-function-name" "^7.8.3" - "@babel/helper-optimise-call-expression" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.6" - "@babel/helper-split-export-declaration" "^7.8.3" - globals "^11.1.0" - -"@babel/plugin-transform-computed-properties@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz#96d0d28b7f7ce4eb5b120bb2e0e943343c86f81b" - integrity sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-destructuring@^7.8.3": - version "7.8.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.8.tgz#fadb2bc8e90ccaf5658de6f8d4d22ff6272a2f4b" - integrity sha512-eRJu4Vs2rmttFCdhPUM3bV0Yo/xPSdPw6ML9KHs/bjB4bLA5HXlbvYXPOD5yASodGod+krjYx21xm1QmL8dCJQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-dotall-regex@^7.4.4", "@babel/plugin-transform-dotall-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz#c3c6ec5ee6125c6993c5cbca20dc8621a9ea7a6e" - integrity sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-duplicate-keys@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz#8d12df309aa537f272899c565ea1768e286e21f1" - integrity sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-exponentiation-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz#581a6d7f56970e06bf51560cd64f5e947b70d7b7" - integrity sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ== - dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-for-of@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz#0f260e27d3e29cd1bb3128da5e76c761aa6c108e" - integrity sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-function-name@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz#279373cb27322aaad67c2683e776dfc47196ed8b" - integrity sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ== - dependencies: - "@babel/helper-function-name" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz#aef239823d91994ec7b68e55193525d76dbd5dc1" - integrity sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-member-expression-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz#963fed4b620ac7cbf6029c755424029fa3a40410" - integrity sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-modules-amd@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz#19755ee721912cf5bb04c07d50280af3484efef4" - integrity sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q== - dependencies: - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helper-plugin-utils" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" - -"@babel/plugin-transform-modules-commonjs@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz#e3e72f4cbc9b4a260e30be0ea59bdf5a39748940" - integrity sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g== - dependencies: - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-simple-access" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" - -"@babel/plugin-transform-modules-systemjs@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz#e9fd46a296fc91e009b64e07ddaa86d6f0edeb90" - integrity sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ== - dependencies: - "@babel/helper-hoist-variables" "^7.8.3" - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helper-plugin-utils" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" - -"@babel/plugin-transform-modules-umd@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz#e909acae276fec280f9b821a5f38e1f08b480697" - integrity sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ== - dependencies: - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-named-capturing-groups-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz#a2a72bffa202ac0e2d0506afd0939c5ecbc48c6c" - integrity sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.8.3" - -"@babel/plugin-transform-new-target@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz#60cc2ae66d85c95ab540eb34babb6434d4c70c43" - integrity sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-object-super@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz#ebb6a1e7a86ffa96858bd6ac0102d65944261725" - integrity sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.3" - -"@babel/plugin-transform-parameters@^7.8.7": - version "7.9.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.3.tgz#3028d0cc20ddc733166c6e9c8534559cee09f54a" - integrity sha512-fzrQFQhp7mIhOzmOtPiKffvCYQSK10NR8t6BBz2yPbeUHb9OLW8RZGtgDRBn8z2hGcwvKDL3vC7ojPTLNxmqEg== - dependencies: - "@babel/helper-get-function-arity" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-property-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz#33194300d8539c1ed28c62ad5087ba3807b98263" - integrity sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-regenerator@^7.8.7": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz#5e46a0dca2bee1ad8285eb0527e6abc9c37672f8" - integrity sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA== - dependencies: - regenerator-transform "^0.14.2" - -"@babel/plugin-transform-reserved-words@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz#9a0635ac4e665d29b162837dd3cc50745dfdf1f5" - integrity sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-shorthand-properties@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz#28545216e023a832d4d3a1185ed492bcfeac08c8" - integrity sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz#9c8ffe8170fdfb88b114ecb920b82fb6e95fe5e8" - integrity sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-sticky-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz#be7a1290f81dae767475452199e1f76d6175b100" - integrity sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-regex" "^7.8.3" - -"@babel/plugin-transform-template-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz#7bfa4732b455ea6a43130adc0ba767ec0e402a80" - integrity sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-typeof-symbol@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz#ede4062315ce0aaf8a657a920858f1a2f35fc412" - integrity sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-unicode-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz#0cef36e3ba73e5c57273effb182f46b91a1ecaad" - integrity sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/preset-env@7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.9.0.tgz#a5fc42480e950ae8f5d9f8f2bbc03f52722df3a8" - integrity sha512-712DeRXT6dyKAM/FMbQTV/FvRCms2hPCx+3weRjZ8iQVQWZejWWk1wwG6ViWMyqb/ouBbGOl5b6aCk0+j1NmsQ== - dependencies: - "@babel/compat-data" "^7.9.0" - "@babel/helper-compilation-targets" "^7.8.7" - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-proposal-async-generator-functions" "^7.8.3" - "@babel/plugin-proposal-dynamic-import" "^7.8.3" - "@babel/plugin-proposal-json-strings" "^7.8.3" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-proposal-numeric-separator" "^7.8.3" - "@babel/plugin-proposal-object-rest-spread" "^7.9.0" - "@babel/plugin-proposal-optional-catch-binding" "^7.8.3" - "@babel/plugin-proposal-optional-chaining" "^7.9.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.8.3" - "@babel/plugin-syntax-async-generators" "^7.8.0" - "@babel/plugin-syntax-dynamic-import" "^7.8.0" - "@babel/plugin-syntax-json-strings" "^7.8.0" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" - "@babel/plugin-syntax-numeric-separator" "^7.8.0" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" - "@babel/plugin-syntax-optional-chaining" "^7.8.0" - "@babel/plugin-syntax-top-level-await" "^7.8.3" - "@babel/plugin-transform-arrow-functions" "^7.8.3" - "@babel/plugin-transform-async-to-generator" "^7.8.3" - "@babel/plugin-transform-block-scoped-functions" "^7.8.3" - "@babel/plugin-transform-block-scoping" "^7.8.3" - "@babel/plugin-transform-classes" "^7.9.0" - "@babel/plugin-transform-computed-properties" "^7.8.3" - "@babel/plugin-transform-destructuring" "^7.8.3" - "@babel/plugin-transform-dotall-regex" "^7.8.3" - "@babel/plugin-transform-duplicate-keys" "^7.8.3" - "@babel/plugin-transform-exponentiation-operator" "^7.8.3" - "@babel/plugin-transform-for-of" "^7.9.0" - "@babel/plugin-transform-function-name" "^7.8.3" - "@babel/plugin-transform-literals" "^7.8.3" - "@babel/plugin-transform-member-expression-literals" "^7.8.3" - "@babel/plugin-transform-modules-amd" "^7.9.0" - "@babel/plugin-transform-modules-commonjs" "^7.9.0" - "@babel/plugin-transform-modules-systemjs" "^7.9.0" - "@babel/plugin-transform-modules-umd" "^7.9.0" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3" - "@babel/plugin-transform-new-target" "^7.8.3" - "@babel/plugin-transform-object-super" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.8.7" - "@babel/plugin-transform-property-literals" "^7.8.3" - "@babel/plugin-transform-regenerator" "^7.8.7" - "@babel/plugin-transform-reserved-words" "^7.8.3" - "@babel/plugin-transform-shorthand-properties" "^7.8.3" - "@babel/plugin-transform-spread" "^7.8.3" - "@babel/plugin-transform-sticky-regex" "^7.8.3" - "@babel/plugin-transform-template-literals" "^7.8.3" - "@babel/plugin-transform-typeof-symbol" "^7.8.4" - "@babel/plugin-transform-unicode-regex" "^7.8.3" - "@babel/preset-modules" "^0.1.3" - "@babel/types" "^7.9.0" - browserslist "^4.9.1" - core-js-compat "^3.6.2" - invariant "^2.2.2" - levenary "^1.1.1" - semver "^5.5.0" - -"@babel/preset-modules@^0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.3.tgz#13242b53b5ef8c883c3cf7dddd55b36ce80fbc72" - integrity sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" - "@babel/plugin-transform-dotall-regex" "^7.4.4" - "@babel/types" "^7.4.4" - esutils "^2.0.2" - -"@babel/runtime@^7.8.4": - version "7.9.2" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" - integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== - dependencies: - regenerator-runtime "^0.13.4" - -"@babel/template@7.8.6", "@babel/template@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" - integrity sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg== - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/parser" "^7.8.6" - "@babel/types" "^7.8.6" - -"@babel/template@^7.4.0", "@babel/template@^7.7.4", "@babel/template@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.3.tgz#e02ad04fe262a657809327f578056ca15fd4d1b8" - integrity sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ== - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/parser" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/traverse@^7.4.3", "@babel/traverse@^7.7.4", "@babel/traverse@^7.8.3", "@babel/traverse@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.8.4.tgz#f0845822365f9d5b0e312ed3959d3f827f869e3c" - integrity sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg== - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.8.4" - "@babel/helper-function-name" "^7.8.3" - "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/parser" "^7.8.4" - "@babel/types" "^7.8.3" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.13" - -"@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.0.tgz#d3882c2830e513f4fe4cec9fe76ea1cc78747892" - integrity sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w== - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.9.0" - "@babel/helper-function-name" "^7.8.3" - "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/parser" "^7.9.0" - "@babel/types" "^7.9.0" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.13" - -"@babel/types@^7.4.0", "@babel/types@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.3.tgz#5a383dffa5416db1b73dedffd311ffd0788fb31c" - integrity sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg== - dependencies: - esutils "^2.0.2" - lodash "^4.17.13" - to-fast-properties "^2.0.0" - -"@babel/types@^7.4.4", "@babel/types@^7.8.6", "@babel/types@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.0.tgz#00b064c3df83ad32b2dbf5ff07312b15c7f1efb5" - integrity sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng== - dependencies: - "@babel/helper-validator-identifier" "^7.9.0" - lodash "^4.17.13" - to-fast-properties "^2.0.0" - -"@firebase/analytics-types@0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@firebase/analytics-types/-/analytics-types-0.3.0.tgz#33c3f695313b561d48d18d663a20f20362d3ee7c" - integrity sha512-0AJ6xn53Qn0D/YOVHHvlWFfnzzRSdd98Lr8Oqe1PJ2HPIN+o7qf03YmOG7fLpR1uplcWd+7vGKmxUrN3jKUBwg== - -"@firebase/analytics@0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.3.0.tgz#943e73e792ba3f282df4d47aff5a1603b93b37a5" - integrity sha512-EEHuK+OcWH6UxufRLVU3lAJ4rmm7aVHmcgkhE9ZQJQy5c+w7QTLvVpGqtrpqD+cYyIBJkFBKJB8NziVyqKwQHw== - dependencies: - "@firebase/analytics-types" "0.3.0" - "@firebase/component" "0.1.8" - "@firebase/installations" "0.4.6" - "@firebase/logger" "0.2.0" - "@firebase/util" "0.2.43" - tslib "1.11.1" - -"@firebase/app-types@0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.5.0.tgz#b9b51a37956ec166debc8784a2fb30b5ffc9e921" - integrity sha512-8j+vCXTpAkYGcFk86mPZ90V6HMFmn196RIEW9Opi0PN+VrPFC1l/eW0gptM8v7VXaQhECOxws3TN2g+dDaeSYA== - -"@firebase/app-types@0.6.0": - version "0.6.0" - resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.6.0.tgz#8dcc3e793c6983e9d54f7eb623a7618c05f2d94c" - integrity sha512-ld6rzjXk/SUauHiQZJkeuSJpxIZ5wdnWuF5fWBFQNPaxsaJ9kyYg9GqEvwZ1z2e6JP5cU9gwRBlfW1WkGtGDYA== - -"@firebase/app@0.6.0": - version "0.6.0" - resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.6.0.tgz#62f1720a8764333d32a11b14f1c46d82f536cf4e" - integrity sha512-utFL07aO64ZVs9g79cv1KHomtLdKkkAeKN5e8G9NlXXuO6dZXhcHLbOmKY1AfwrkAvUzPEKkFFY3dytOIt+nlg== - dependencies: - "@firebase/app-types" "0.6.0" - "@firebase/component" "0.1.8" - "@firebase/logger" "0.2.0" - "@firebase/util" "0.2.43" - dom-storage "2.1.0" - tslib "1.11.1" - xmlhttprequest "1.8.0" - -"@firebase/auth-interop-types@0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@firebase/auth-interop-types/-/auth-interop-types-0.1.1.tgz#b3e1bc5ea8b2df1c376b5fc14aae8a3572dbcace" - integrity sha512-rNpCOyCspZvNDoQVQLQQgWAGBMB2ClCWKN1c8cEFgLNFgnMJrjVB+tcL7KW2q2UjKa7l8Mxgwys7szTiEDAcvA== - -"@firebase/auth-interop-types@0.1.4": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@firebase/auth-interop-types/-/auth-interop-types-0.1.4.tgz#e81589f58508630a5bffa604d7c949a0d01ea97b" - integrity sha512-CLKNS84KGAv5lRnHTQZFWoR11Ti7gIPFirDDXWek/fSU+TdYdnxJFR5XSD4OuGyzUYQ3Dq7aVj5teiRdyBl9hA== - -"@firebase/auth-types@0.10.0": - version "0.10.0" - resolved "https://registry.yarnpkg.com/@firebase/auth-types/-/auth-types-0.10.0.tgz#9403633e723336055fad4bbf5e4c9fe3c55f8d3f" - integrity sha512-VuW7c+RAk3AYPU0Hxmun3RzXn7fbJDdjQbxvvpRMnQ9zrhk8mH42cY466M0n4e/UGQ+0smlx5BqZII8aYQ5XPg== - -"@firebase/auth@0.14.1": - version "0.14.1" - resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-0.14.1.tgz#0cb3226025c27bf2e01a738f3841ab6bde80572e" - integrity sha512-LE+QED10cjp28jJ7wwIY58HQBXoJioEWiQy7iQS8Fo2UxHGY5BvGjwnxX4yyszQPbcZZRLDSlBIUaYfog+rdEA== - dependencies: - "@firebase/auth-types" "0.10.0" - -"@firebase/component@0.1.4": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.1.4.tgz#ed348a3e2997918b1c4ea13103b7b3e216c12ab9" - integrity sha512-k3JZFUyHnSWC/7v+x+pIHLDNJPYA6xd7nqrQASOXH5TXhCR9meg0VsnJb+knD18491iRMKJnQWNSHdqPK9AX5w== - dependencies: - "@firebase/util" "0.2.39" - tslib "1.10.0" - -"@firebase/component@0.1.8": - version "0.1.8" - resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.1.8.tgz#3a5753493ba65c85c9c09e2707be44d73e0a456c" - integrity sha512-kzuCF+NVympQk3gcsHldOmDRVPVndECi6O9Wvd47HTEQYO9HsZWfOM1fHUvvHAijSzNi16p4NSM7UziuBQBL4w== - dependencies: - "@firebase/util" "0.2.43" - tslib "1.11.1" - -"@firebase/database-types@0.4.10": - version "0.4.10" - resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-0.4.10.tgz#baa10bc78cfb57dd6159e0264b8305994a8e24d0" - integrity sha512-66puLsckt5HASgRN3CfhLn2iuGrgCjfH3u17OL0f5MtEweYLx+yW2QW5d539Wx30xD4B+INEdaRetw6xEa9t7g== - dependencies: - "@firebase/app-types" "0.5.0" - -"@firebase/database-types@0.4.14": - version "0.4.14" - resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-0.4.14.tgz#181e10c1d1ae64fd0a080f6e0369cec115c51d70" - integrity sha512-+D41HWac0HcvwMi+0dezEdSOZHpVjPKPNmpQiW2GDuS5kk27/v1jxc9v7F4ALLtpxbVcn16UZl5PqEkcS9H2Xg== - dependencies: - "@firebase/app-types" "0.6.0" - -"@firebase/database@0.5.24": - version "0.5.24" - resolved "https://registry.yarnpkg.com/@firebase/database/-/database-0.5.24.tgz#548b2030def35a7b6f4d71a4b2d1a67499d1eef3" - integrity sha512-9whAQzU8cxDUKGBWCT/aHVmqfyzCP2RkGhbZi2oHpMrmvht7cuBtXtUbDD5R8WomniCOUP8rtQfmCFI7V9ehYw== - dependencies: - "@firebase/auth-interop-types" "0.1.4" - "@firebase/component" "0.1.8" - "@firebase/database-types" "0.4.14" - "@firebase/logger" "0.2.0" - "@firebase/util" "0.2.43" - faye-websocket "0.11.3" - tslib "1.11.1" - -"@firebase/database@^0.5.17": - version "0.5.20" - resolved "https://registry.yarnpkg.com/@firebase/database/-/database-0.5.20.tgz#f157e04409a94dda90ebb5493cc639310242cdea" - integrity sha512-31dNLqMW4nGrGzIDS5hh+1LFzkr/m1Kb+EcftQGC3NaGC3zHwGyG7ijn+Xo7gIWtXukvJvm1cFC7R+eOCPEejw== - dependencies: - "@firebase/auth-interop-types" "0.1.1" - "@firebase/component" "0.1.4" - "@firebase/database-types" "0.4.10" - "@firebase/logger" "0.1.34" - "@firebase/util" "0.2.39" - faye-websocket "0.11.3" - tslib "1.10.0" - -"@firebase/firestore-types@1.10.1": - version "1.10.1" - resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-1.10.1.tgz#bf018f9c495f470592de745389474dc1c2960d3f" - integrity sha512-vyKdm+AYUFT8XeUX62IOqaqPFCs/mAMoSEsqIz9HnSVsqCw/IocNjtjSa+3M80kRw4V8fI7JI+Xz6Wg5VJXLqA== - -"@firebase/firestore@1.13.0": - version "1.13.0" - resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-1.13.0.tgz#2638f25b2a3d19cb891a6b334a0dc14aefef5a34" - integrity sha512-GctO+sxLqOnY8SkBN5Z5p1nUYRX+yWSc9Kcx9nIPbUZ0WqBM5BaSlBHZHTFtjmJxS+0j/Y8Mu7c6qm6q5rVnjA== - dependencies: - "@firebase/component" "0.1.8" - "@firebase/firestore-types" "1.10.1" - "@firebase/logger" "0.2.0" - "@firebase/util" "0.2.43" - "@firebase/webchannel-wrapper" "0.2.38" - "@grpc/proto-loader" "^0.5.0" - grpc "1.24.2" - tslib "1.11.1" - -"@firebase/functions-types@0.3.16": - version "0.3.16" - resolved "https://registry.yarnpkg.com/@firebase/functions-types/-/functions-types-0.3.16.tgz#be0362d7f61648fdf36a7d95de239eddee88f931" - integrity sha512-kHhBvSYiY2prY4vNQCALYs1+OruTdylvGemHG6G6Bs/rj3qw7ui3WysBsDU/rInJitHIcsZ35qrtanoJeQUIXQ== - -"@firebase/functions@0.4.38": - version "0.4.38" - resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.4.38.tgz#f49e3f4a4a3c67e527c472f05b2e517ea057e7fc" - integrity sha512-t5QkJg251FmIEEi2mh3Xrf7Q3yonSLRaUW/vhgze7A3Xy3uTIUT3BVNWuKaRmg1n0PgKQaBHCjlDoLJAdSpujg== - dependencies: - "@firebase/component" "0.1.8" - "@firebase/functions-types" "0.3.16" - "@firebase/messaging-types" "0.4.4" - isomorphic-fetch "2.2.1" - tslib "1.11.1" - -"@firebase/installations-types@0.3.3": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@firebase/installations-types/-/installations-types-0.3.3.tgz#f2e49e73afaeb7b352250365d0d90dff0b792592" - integrity sha512-XvWhPPAGeZlc+CfCA8jTt2pv19Jovi/nUV73u30QbjBbk5xci9bp5I29aBZukHsR6YNBjFCLSkLPbno4m/bLUg== - -"@firebase/installations@0.4.6": - version "0.4.6" - resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.4.6.tgz#e288e18b39dda3e8b3b35c3be6185a7b187924e5" - integrity sha512-ey8cE2ldRO4pYqg0lCWQ+XFLETHZWha3Hw1CnYJjLivk4FMM8u/es3Oa257wwtYXAUfr0UsPDfHFgYME9E9EhA== - dependencies: - "@firebase/component" "0.1.8" - "@firebase/installations-types" "0.3.3" - "@firebase/util" "0.2.43" - idb "3.0.2" - tslib "1.11.1" - -"@firebase/logger@0.1.34": - version "0.1.34" - resolved "https://registry.yarnpkg.com/@firebase/logger/-/logger-0.1.34.tgz#8fd52f73c9de02d2a96f3a88c692e3f9a25297f9" - integrity sha512-J2h6ylpd1IcuonRM3HBdXThitds6aQSIeoPYRPvApSFy82NhFPKRzJlflAhlQWjJOh59/jyQBGWJNxCL6fp4hw== - -"@firebase/logger@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@firebase/logger/-/logger-0.2.0.tgz#d40149b8a33bca3dfbfb5b4a63e06b3ffa193157" - integrity sha512-qOMnAh1JY9NkYUEy3iFviiFq0dCvk6qN2DsRy2Y7eAhHR6RqwA47l1kI+0MIXmSzlJ9akXjWAXxV5ijzr68Big== - -"@firebase/messaging-types@0.4.4": - version "0.4.4" - resolved "https://registry.yarnpkg.com/@firebase/messaging-types/-/messaging-types-0.4.4.tgz#bef66157bdd3ddaafd6d48f1c5ee973fdc385f84" - integrity sha512-JGtkr+1A1Dw7+yCqQigqBfGKtq0gTCruFScBD4MVjqZHiqGIYpnQisWnpGbkzPR6aOt6iQxgwxUhHG1ulUQGeg== - -"@firebase/messaging@0.6.10": - version "0.6.10" - resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.6.10.tgz#0f2713a85732ce23ec7968e2f0ac784a259330e4" - integrity sha512-WYnKEffG6m0EMHzib8KCWVUGno1cuBC13RrOfGWOCv/whdy9QCIZgMxH/NsY3BrYst8FnjuXEU16fi5AEf4qbg== - dependencies: - "@firebase/component" "0.1.8" - "@firebase/installations" "0.4.6" - "@firebase/messaging-types" "0.4.4" - "@firebase/util" "0.2.43" - idb "3.0.2" - tslib "1.11.1" - -"@firebase/performance-types@0.0.12": - version "0.0.12" - resolved "https://registry.yarnpkg.com/@firebase/performance-types/-/performance-types-0.0.12.tgz#15fa79e296b502e21054a66c9e7ded59398fd8a7" - integrity sha512-eIDF7CHetOE5sc+hCaUebEn/2Aiaju7UkgZDTl7lNQHz5fK9wJ/11HaE8WdnDr//ngS3lQAGC2RB4lAZeEWraA== - -"@firebase/performance@0.2.36": - version "0.2.36" - resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.2.36.tgz#341bad35e786a377a38c4b4165088ebd4f07d025" - integrity sha512-nMx3gT+ZD86MV5n460XFA1o75YYMGcs2MXrJa462rfUQtqOrtOOvdUvVtmE6cLrHsL4Y83B+VBWKHzACIPghPw== - dependencies: - "@firebase/component" "0.1.8" - "@firebase/installations" "0.4.6" - "@firebase/logger" "0.2.0" - "@firebase/performance-types" "0.0.12" - "@firebase/util" "0.2.43" - tslib "1.11.1" - -"@firebase/polyfill@0.3.33": - version "0.3.33" - resolved "https://registry.yarnpkg.com/@firebase/polyfill/-/polyfill-0.3.33.tgz#93974c68ca092a9210f02b803f5e285e86b547ee" - integrity sha512-Arp9JViyD2i0K01NCCY0WZK5p16kQB/wddf44+Qboh+u3eIrFbVk0OO2IknjrkzIW392u73Ts7TkVxLPGPJF9g== - dependencies: - core-js "3.6.4" - promise-polyfill "8.1.3" - whatwg-fetch "2.0.4" - -"@firebase/remote-config-types@0.1.8": - version "0.1.8" - resolved "https://registry.yarnpkg.com/@firebase/remote-config-types/-/remote-config-types-0.1.8.tgz#0c8d8a839621230053ba55704b5d1145bfe54daa" - integrity sha512-K12IBHO7OD4gCW0FEqZL9zMqVAfS4+joC4YIn3bHezZfu3RL+Bw1wCb0cAD7RfDPcQxWJjxOHpce4YhuqSxPFA== - -"@firebase/remote-config@0.1.17": - version "0.1.17" - resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.1.17.tgz#ac4a8c9def48d404bdfe72cdf47199360cd44d01" - integrity sha512-jIRHXih0krVTNGYMewFVIaX8WPE1iS06fV4oMMHCCSSforGodv535uVZZ41Il29Q+22zOcyJvahoc990V0cFoA== - dependencies: - "@firebase/component" "0.1.8" - "@firebase/installations" "0.4.6" - "@firebase/logger" "0.2.0" - "@firebase/remote-config-types" "0.1.8" - "@firebase/util" "0.2.43" - tslib "1.11.1" - -"@firebase/storage-types@0.3.11": - version "0.3.11" - resolved "https://registry.yarnpkg.com/@firebase/storage-types/-/storage-types-0.3.11.tgz#98f6ced5460502ab12778ce71d4dc9bf0ab7f2ee" - integrity sha512-EMOo5aeiJIa8eQ/VqjIa/DYlDcEJX1V84FOxmLfNWZIlmCSvcqx9E9mcNlOnoUB4iePqQjTMQRtKlIBvvEVhVg== - -"@firebase/storage@0.3.30": - version "0.3.30" - resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.3.30.tgz#195f6bd9b526710b7446e430b71dfb82cf8b35cc" - integrity sha512-wapt4+NiEqTiLKPpsy+XbdLTN99pkqjf46Z7zqeS+vh+61cJsUT8M7YBfBb0ZN+dY6gnI5QNzviiKpykhJQbVA== - dependencies: - "@firebase/component" "0.1.8" - "@firebase/storage-types" "0.3.11" - "@firebase/util" "0.2.43" - tslib "1.11.1" - -"@firebase/util@0.2.39": - version "0.2.39" - resolved "https://registry.yarnpkg.com/@firebase/util/-/util-0.2.39.tgz#4387b12c315857081f595bba7229b0cabadb754f" - integrity sha512-hxbQJ9TkFzd6g8/ZcWBjdrxjxS0jYnR1EN3i1ah7i3KtvuxAtwNJ04YRf0QhKhCoitTkJ1Yn3cGb0kFnGtJVRA== - dependencies: - tslib "1.10.0" - -"@firebase/util@0.2.43": - version "0.2.43" - resolved "https://registry.yarnpkg.com/@firebase/util/-/util-0.2.43.tgz#551728e1f6deb3a3709c2e9dc60dbb7c1a423fd4" - integrity sha512-4gGlvcoOJ48xO6PH59UOHLjvImdYXANF/1d0ao60fbiJDIKxJqMksXw3UF2zsUrRkyCOqIDLeiVuF18vffXP+g== - dependencies: - tslib "1.11.1" - -"@firebase/webchannel-wrapper@0.2.38": - version "0.2.38" - resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.2.38.tgz#1f0602cd73f7402ffc4d6116c811dfbb652caa73" - integrity sha512-mp1XmAJsuqaSWm5WQYo7R0zfZWe9EmwMCxsxkKr+ubLOumyNy4NG5aV45hEpFTosQv4myXpiCiS4GFE9mNqLZQ== - -"@google-cloud/common@^2.1.1": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@google-cloud/common/-/common-2.3.0.tgz#492ddd3a163c9c335ce596d70f8821fa83603a64" - integrity sha512-nmIyi3q/FL2j6ZJ61xK/863DoJEZayI2/W/iCgwrCYUYsem277XO45MBTAimjgiKBCA0c9InmQyfT48h/IK4jg== - dependencies: - "@google-cloud/projectify" "^1.0.0" - "@google-cloud/promisify" "^1.0.0" - arrify "^2.0.0" - duplexify "^3.6.0" - ent "^2.2.0" - extend "^3.0.2" - google-auth-library "^5.5.0" - retry-request "^4.0.0" - teeny-request "^6.0.0" - -"@google-cloud/firestore@^3.0.0": - version "3.4.1" - resolved "https://registry.yarnpkg.com/@google-cloud/firestore/-/firestore-3.4.1.tgz#10bb1deaf518f622bd96cacd476d436816e16c09" - integrity sha512-k3PPcLvP9wr4yyA0djzfPdj2ZewburifhpcFACa0wiXvjXj3Ob68MORcPC3a3wyonX73TY72RsEGPk4Ult0Dyw== - dependencies: - deep-equal "^2.0.0" - functional-red-black-tree "^1.0.1" - google-gax "^1.13.0" - readable-stream "^3.4.0" - through2 "^3.0.0" - -"@google-cloud/paginator@^2.0.0": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@google-cloud/paginator/-/paginator-2.0.3.tgz#c7987ad05d1c3ebcef554381be80e9e8da4e4882" - integrity sha512-kp/pkb2p/p0d8/SKUu4mOq8+HGwF8NPzHWkj+VKrIPQPyMRw8deZtrO/OcSiy9C/7bpfU5Txah5ltUNfPkgEXg== - dependencies: - arrify "^2.0.0" - extend "^3.0.2" - -"@google-cloud/precise-date@^1.0.0": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@google-cloud/precise-date/-/precise-date-1.0.3.tgz#39c600ed52213f4158692a72c90d13b2162a93d2" - integrity sha512-wWnDGh9y3cJHLuVEY8t6un78vizzMWsS7oIWKeFtPj+Ndy+dXvHW0HTx29ZUhen+tswSlQYlwFubvuRP5kKdzQ== - -"@google-cloud/projectify@^1.0.0": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@google-cloud/projectify/-/projectify-1.0.4.tgz#28daabebba6579ed998edcadf1a8f3be17f3b5f0" - integrity sha512-ZdzQUN02eRsmTKfBj9FDL0KNDIFNjBn/d6tHQmA/+FImH5DO6ZV8E7FzxMgAUiVAUq41RFAkb25p1oHOZ8psfg== - -"@google-cloud/promisify@^1.0.0": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@google-cloud/promisify/-/promisify-1.0.4.tgz#ce86ffa94f9cfafa2e68f7b3e4a7fad194189723" - integrity sha512-VccZDcOql77obTnFh0TbNED/6ZbbmHDf8UMNnzO1d5g9V0Htfm4k5cllY8P1tJsRKC3zWYGRLaViiupcgVjBoQ== - -"@google-cloud/pubsub@^1.1.5": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@google-cloud/pubsub/-/pubsub-1.4.1.tgz#3cf45c74c6073bef0927b22a3a6fc205960b4a4f" - integrity sha512-WDUrGisO6Xiy9+N00dzOvgc0ZV8hgwEzYQCacYwLU3DAlDm6ad/QR6TubJE2OHQ6VcCtzKQPMMi7yt1iCu75NA== - dependencies: - "@google-cloud/paginator" "^2.0.0" - "@google-cloud/precise-date" "^1.0.0" - "@google-cloud/projectify" "^1.0.0" - "@google-cloud/promisify" "^1.0.0" - "@types/duplexify" "^3.6.0" - "@types/long" "^4.0.0" - arrify "^2.0.0" - async-each "^1.0.1" - extend "^3.0.2" - google-auth-library "^5.5.0" - google-gax "^1.7.5" - is-stream-ended "^0.1.4" - lodash.snakecase "^4.1.1" - p-defer "^3.0.0" - protobufjs "^6.8.1" - -"@google-cloud/storage@^4.1.2": - version "4.3.0" - resolved "https://registry.yarnpkg.com/@google-cloud/storage/-/storage-4.3.0.tgz#90118c42817fb2c8b3b8663a0f2857b5b45dda78" - integrity sha512-ph0jsUsZ9FPtN40V5eIkKPLUmxnTpxqBDkWxStW/kbQZgoNVGW9vJcbsYSyE4ath7jQIpM4OHu6aqmPFX1OnGw== - dependencies: - "@google-cloud/common" "^2.1.1" - "@google-cloud/paginator" "^2.0.0" - "@google-cloud/promisify" "^1.0.0" - arrify "^2.0.0" - compressible "^2.0.12" - concat-stream "^2.0.0" - date-and-time "^0.12.0" - duplexify "^3.5.0" - extend "^3.0.2" - gaxios "^2.0.1" - gcs-resumable-upload "^2.2.4" - hash-stream-validation "^0.2.2" - mime "^2.2.0" - mime-types "^2.0.8" - onetime "^5.1.0" - p-limit "^2.2.0" - pumpify "^2.0.0" - readable-stream "^3.4.0" - snakeize "^0.1.0" - stream-events "^1.0.1" - through2 "^3.0.0" - xdg-basedir "^4.0.0" - -"@grpc/grpc-js@^0.6.12": - version "0.6.15" - resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-0.6.15.tgz#534d1051ddced4e5e5849212789dd64014214dd4" - integrity sha512-BFK5YMu8JILedibo0nr3NYM0ZC5hCZuXtzk10wEUp3d3pH11PjdvTfN1yEJ0VsfBY5Gtp3WOQ+t7Byq0NzH/iQ== - dependencies: - semver "^6.2.0" - -"@grpc/proto-loader@^0.5.0", "@grpc/proto-loader@^0.5.1": - version "0.5.3" - resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.5.3.tgz#a233070720bf7560c4d70e29e7950c72549a132c" - integrity sha512-8qvUtGg77G2ZT2HqdqYoM/OY97gQd/0crSG34xNmZ4ZOsv3aQT/FQV9QfZPazTGna6MIoyUd+u6AxsoZjJ/VMQ== - dependencies: - lodash.camelcase "^4.3.0" - protobufjs "^6.8.6" - -"@istanbuljs/schema@^0.1.2": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" - integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== - -"@jsdevtools/coverage-istanbul-loader@3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@jsdevtools/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.3.tgz#102e414b02ae2f0b3c7fd45a705601e1fd4867c5" - integrity sha512-TAdNkeGB5Fe4Og+ZkAr1Kvn9by2sfL44IAHFtxlh1BA1XJ5cLpO9iSNki5opWESv3l3vSHsZ9BNKuqFKbEbFaA== - dependencies: - convert-source-map "^1.7.0" - istanbul-lib-instrument "^4.0.1" - loader-utils "^1.4.0" - merge-source-map "^1.1.0" - schema-utils "^2.6.4" - -"@ngtools/webpack@9.1.9": - version "9.1.9" - resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-9.1.9.tgz#2098afd6f1f028238468262df2ad10afa81b82a5" - integrity sha512-7UzRL54JcK1m2PvgpchV26TF9OYipj/8+dygdjaidqOyQujWpHncdtq/ScbUsoJ44nHjIRJPhaGE00NLpWZvnA== - dependencies: - "@angular-devkit/core" "9.1.9" - enhanced-resolve "4.1.1" - rxjs "6.5.4" - webpack-sources "1.4.3" - -"@nodelib/fs.scandir@2.1.3": - version "2.1.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" - integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== - dependencies: - "@nodelib/fs.stat" "2.0.3" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" - integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" - integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== - dependencies: - "@nodelib/fs.scandir" "2.1.3" - fastq "^1.6.0" - -"@npmcli/move-file@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.0.1.tgz#de103070dac0f48ce49cf6693c23af59c0f70464" - integrity sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw== - dependencies: - mkdirp "^1.0.4" - -"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" - integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78= - -"@protobufjs/base64@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" - integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== - -"@protobufjs/codegen@^2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" - integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== - -"@protobufjs/eventemitter@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" - integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A= - -"@protobufjs/fetch@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" - integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU= - dependencies: - "@protobufjs/aspromise" "^1.1.1" - "@protobufjs/inquire" "^1.1.0" - -"@protobufjs/float@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" - integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E= - -"@protobufjs/inquire@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" - integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik= - -"@protobufjs/path@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" - integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0= - -"@protobufjs/pool@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" - integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q= - -"@protobufjs/utf8@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" - integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= - -"@rollup/plugin-commonjs@^13.0.0": - version "13.0.0" - resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-13.0.0.tgz#8a1d684ba6848afe8b9e3d85649d4b2f6f7217ec" - integrity sha512-Anxc3qgkAi7peAyesTqGYidG5GRim9jtg8xhmykNaZkImtvjA7Wsqep08D2mYsqw1IF7rA3lYfciLgzUSgRoqw== - dependencies: - "@rollup/pluginutils" "^3.0.8" - commondir "^1.0.1" - estree-walker "^1.0.1" - glob "^7.1.2" - is-reference "^1.1.2" - magic-string "^0.25.2" - resolve "^1.11.0" - -"@rollup/plugin-json@^4.0.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-4.1.0.tgz#54e09867ae6963c593844d8bd7a9c718294496f3" - integrity sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw== - dependencies: - "@rollup/pluginutils" "^3.0.8" - -"@rollup/plugin-node-resolve@^8.0.0": - version "8.1.0" - resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-8.1.0.tgz#1da5f3d0ccabc8f66f5e3c74462aad76cfd96c47" - integrity sha512-ovq7ZM3JJYUUmEjjO+H8tnUdmQmdQudJB7xruX8LFZ1W2q8jXdPUS6SsIYip8ByOApu4RR7729Am9WhCeCMiHA== - dependencies: - "@rollup/pluginutils" "^3.0.8" - "@types/resolve" "0.0.8" - builtin-modules "^3.1.0" - deep-freeze "^0.0.1" - deepmerge "^4.2.2" - is-module "^1.0.0" - resolve "^1.14.2" - -"@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.0.9": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" - integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== - dependencies: - "@types/estree" "0.0.39" - estree-walker "^1.0.1" - picomatch "^2.2.2" - -"@schematics/angular@9.1.9": - version "9.1.9" - resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-9.1.9.tgz#df9f30dd7b63856121fbb9ab5dda57b443802a33" - integrity sha512-c8YGZ6pDfr8IDD1qaOjlEBAkEz14KFSxDj0hCWs0xIM0py513tu5sW8+ziYxGG4bgqpsgVR/KAxuY78iBfUVag== - dependencies: - "@angular-devkit/core" "9.1.9" - "@angular-devkit/schematics" "9.1.9" - -"@schematics/angular@^8.3.21": - version "8.3.24" - resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-8.3.24.tgz#4d8bfbeed2849bed611e682f418bfcadb0c62277" - integrity sha512-0nf/LgMHAvhjWS97Pl3JGMqS9/4PI+C0+vJoAo6D7ax8Fb+wuY5uD6Pb7ZqaZALlEnqTgE+FBQ1K8VBVbuwKbA== - dependencies: - "@angular-devkit/core" "8.3.24" - "@angular-devkit/schematics" "8.3.24" - -"@schematics/update@0.901.9": - version "0.901.9" - resolved "https://registry.yarnpkg.com/@schematics/update/-/update-0.901.9.tgz#6285611910ebf8bb4078447d2ba32931595efcd4" - integrity sha512-VChX0VO/oyfCF3y+HjMTU2qN3vGgJYxEI1V+Q9aAlwl95t3GAufuaFY1CNW3YV4XkYIjD88e3yWl8d5yO4qf4w== - dependencies: - "@angular-devkit/core" "9.1.9" - "@angular-devkit/schematics" "9.1.9" - "@yarnpkg/lockfile" "1.1.0" - ini "1.3.5" - npm-package-arg "^8.0.0" - pacote "9.5.12" - rxjs "6.5.4" - semver "7.1.3" - semver-intersect "1.4.0" - -"@schematics/update@^0.803.21": - version "0.803.24" - resolved "https://registry.yarnpkg.com/@schematics/update/-/update-0.803.24.tgz#f1076b6b1e4aa997f2bbe6db392089bb467787a6" - integrity sha512-NvCKn3QfpRjx1EzL56q9IC9fRtDXZP4bMGs/2tj+wtdBNHgm6ZJMJ9qc4mGeztKGbDFLmnX3Xz0XawAl+KeYzQ== - dependencies: - "@angular-devkit/core" "8.3.24" - "@angular-devkit/schematics" "8.3.24" - "@yarnpkg/lockfile" "1.1.0" - ini "1.3.5" - pacote "9.5.5" - rxjs "6.4.0" - semver "6.3.0" - semver-intersect "1.4.0" - -"@sindresorhus/is@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" - integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== - -"@szmarczak/http-timer@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" - integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== - dependencies: - defer-to-connect "^1.0.1" - -"@types/body-parser@*": - version "1.17.1" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.17.1.tgz#18fcf61768fb5c30ccc508c21d6fd2e8b3bf7897" - integrity sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/bytebuffer@^5.0.40": - version "5.0.40" - resolved "https://registry.yarnpkg.com/@types/bytebuffer/-/bytebuffer-5.0.40.tgz#d6faac40dcfb09cd856cdc4c01d3690ba536d3ee" - integrity sha512-h48dyzZrPMz25K6Q4+NCwWaxwXany2FhQg/ErOcdZS1ZpsaDnDMZg8JYLMTGz7uvXKrcKGJUZJlZObyfgdaN9g== - dependencies: - "@types/long" "*" - "@types/node" "*" - -"@types/color-name@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" - integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== - -"@types/connect@*": - version "3.4.33" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.33.tgz#31610c901eca573b8713c3330abc6e6b9f588546" - integrity sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A== - dependencies: - "@types/node" "*" - -"@types/duplexify@^3.6.0": - version "3.6.0" - resolved "https://registry.yarnpkg.com/@types/duplexify/-/duplexify-3.6.0.tgz#dfc82b64bd3a2168f5bd26444af165bf0237dcd8" - integrity sha512-5zOA53RUlzN74bvrSGwjudssD9F3a797sDZQkiYpUOxW+WHaXTCPz4/d5Dgi6FKnOqZ2CpaTo0DhgIfsXAOE/A== - dependencies: - "@types/node" "*" - -"@types/estree@0.0.39": - version "0.0.39" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" - integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== - -"@types/events@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" - integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== - -"@types/express-serve-static-core@*": - version "4.17.2" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.2.tgz#f6f41fa35d42e79dbf6610eccbb2637e6008a0cf" - integrity sha512-El9yMpctM6tORDAiBwZVLMcxoTMcqqRO9dVyYcn7ycLWbvR8klrDn8CAOwRfZujZtWD7yS/mshTdz43jMOejbg== - dependencies: - "@types/node" "*" - "@types/range-parser" "*" - -"@types/express@^4.17.3": - version "4.17.4" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.4.tgz#e78bf09f3f530889575f4da8a94cd45384520aac" - integrity sha512-DO1L53rGqIDUEvOjJKmbMEQ5Z+BM2cIEPy/eV3En+s166Gz+FeuzRerxcab757u/U4v4XF4RYrZPmqKa+aY/2w== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "*" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/form-data@0.0.*": - version "0.0.33" - resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-0.0.33.tgz#c9ac85b2a5fd18435b8c85d9ecb50e6d6c893ff8" - integrity sha1-yayFsqX9GENbjIXZ7LUObWyJP/g= - dependencies: - "@types/node" "*" - -"@types/fs-extra@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-7.0.0.tgz#9c4ad9e1339e7448a76698829def1f159c1b636c" - integrity sha512-ndoMMbGyuToTy4qB6Lex/inR98nPiNHacsgMPvy+zqMLgSxbt8VtWpDArpGp69h1fEDQHn1KB+9DWD++wgbwYA== - dependencies: - "@types/node" "*" - -"@types/fs-extra@^8.0.1": - version "8.0.1" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.0.1.tgz#a2378d6e7e8afea1564e44aafa2e207dadf77686" - integrity sha512-J00cVDALmi/hJOYsunyT52Hva5TnJeKP5yd1r+mH/ZU0mbYZflR0Z5kw5kITtKTRYMhm1JMClOFYdHnQszEvqw== - dependencies: - "@types/node" "*" - -"@types/glob@^7.1.1": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" - integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== - dependencies: - "@types/events" "*" - "@types/minimatch" "*" - "@types/node" "*" - -"@types/gzip-size@^5.1.1": - version "5.1.1" - resolved "https://registry.yarnpkg.com/@types/gzip-size/-/gzip-size-5.1.1.tgz#c1ca97523c9dc9161c310a2a5bea645dcbd265d1" - integrity sha512-iQ89BEQwrOg2D+sM8IBRmB8omQo9GDxrM1ICDVcdiM9zcNFHCW9lcHU+bqvbRWwKpbUS5NZO2PYAqXfCjeWgXQ== - dependencies: - gzip-size "*" - -"@types/inquirer@^0.0.44": - version "0.0.44" - resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-0.0.44.tgz#60ce954581cfdf44ad3899ec4cdc5fbe3fef1694" - integrity sha512-ugbhy1yBtCz5iTWYF+AGRS/UcMcWicdyHhxl9VaeFYc3ueg0CCssthQLB3rIcIOeGtfG6WPEvHdLu/IjKYfefg== - dependencies: - "@types/rx" "*" - "@types/through" "*" - -"@types/jasmine@^3.3.13": - version "3.5.2" - resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.5.2.tgz#9ff982e0ddca82f65e5d85a3f0584e9e6b92c47b" - integrity sha512-7hrdBDFWlTb4EhrXYRyC7i3L2kKCV0TqYbzuV+gwyPNF2V4SSHw2Vs223ai26W4tEg+t4e9Wfi1vW6JLubYPiw== - -"@types/json-schema@^7.0.4": - version "7.0.5" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd" - integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ== - -"@types/json5@^0.0.29": - version "0.0.29" - resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" - integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= - -"@types/lodash@^4.14.104": - version "4.14.149" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440" - integrity sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ== - -"@types/long@*", "@types/long@^4.0.0": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" - integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== - -"@types/mime@*": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" - integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw== - -"@types/minimatch@*", "@types/minimatch@3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" - integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== - -"@types/node@*": - version "13.7.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.0.tgz#b417deda18cf8400f278733499ad5547ed1abec4" - integrity sha512-GnZbirvmqZUzMgkFn70c74OQpTTUcCzlhQliTzYjQMqg+hVKcDnxdL19Ne3UdYzdMA/+W3eb646FWn/ZaT1NfQ== - -"@types/node@6.0.*": - version "6.0.118" - resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.118.tgz#8014a9b1dee0b72b4d7cd142563f1af21241c3a2" - integrity sha512-N33cKXGSqhOYaPiT4xUGsYlPPDwFtQM/6QxJxuMXA/7BcySW+lkn2yigWP7vfs4daiL/7NJNU6DMCqg5N4B+xQ== - -"@types/node@^10.1.0": - version "10.17.14" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.14.tgz#b6c60ebf2fb5e4229fdd751ff9ddfae0f5f31541" - integrity sha512-G0UmX5uKEmW+ZAhmZ6PLTQ5eu/VPaT+d/tdLd5IFsKRPcbe6lPxocBtcYBFSaLaCW8O60AX90e91Nsp8lVHCNw== - -"@types/node@^12.6.2": - version "12.12.26" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.26.tgz#213e153babac0ed169d44a6d919501e68f59dea9" - integrity sha512-UmUm94/QZvU5xLcUlNR8hA7Ac+fGpO1EG/a8bcWVz0P0LqtxFmun9Y2bbtuckwGboWJIT70DoWq1r3hb56n3DA== - -"@types/node@^8.10.59": - version "8.10.59" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.59.tgz#9e34261f30183f9777017a13d185dfac6b899e04" - integrity sha512-8RkBivJrDCyPpBXhVZcjh7cQxVBSmRk9QM7hOketZzp6Tg79c0N8kkpAIito9bnJ3HCVCHVYz+KHTEbfQNfeVQ== - -"@types/normalize-package-data@^2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" - integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== - -"@types/parse-json@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" - integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== - -"@types/q@^1.5.1": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" - integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== - -"@types/qs@*": - version "6.9.1" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.1.tgz#937fab3194766256ee09fcd40b781740758617e7" - integrity sha512-lhbQXx9HKZAPgBkISrBcmAcMpZsmpe/Cd/hY7LGZS5OfkySUBItnPZHgQPssWYUET8elF+yCFBbP1Q0RZPTdaw== - -"@types/range-parser@*": - version "1.2.3" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" - integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== - -"@types/request@0.0.30": - version "0.0.30" - resolved "https://registry.yarnpkg.com/@types/request/-/request-0.0.30.tgz#18208841a0cf6538eff5e306bfa92e86c8c8acae" - integrity sha1-GCCIQaDPZTjv9eMGv6kuhsjIrK4= - dependencies: - "@types/form-data" "0.0.*" - "@types/node" "6.0.*" - -"@types/resolve@0.0.8": - version "0.0.8" - resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" - integrity sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ== - dependencies: - "@types/node" "*" - -"@types/rx-core-binding@*": - version "4.0.4" - resolved "https://registry.yarnpkg.com/@types/rx-core-binding/-/rx-core-binding-4.0.4.tgz#d969d32f15a62b89e2862c17b3ee78fe329818d3" - integrity sha512-5pkfxnC4w810LqBPUwP5bg7SFR/USwhMSaAeZQQbEHeBp57pjKXRlXmqpMrLJB4y1oglR/c2502853uN0I+DAQ== - dependencies: - "@types/rx-core" "*" - -"@types/rx-core@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-core/-/rx-core-4.0.3.tgz#0b3354b1238cedbe2b74f6326f139dbc7a591d60" - integrity sha1-CzNUsSOM7b4rdPYybxOdvHpZHWA= - -"@types/rx-lite-aggregates@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-lite-aggregates/-/rx-lite-aggregates-4.0.3.tgz#6efb2b7f3d5f07183a1cb2bd4b1371d7073384c2" - integrity sha512-MAGDAHy8cRatm94FDduhJF+iNS5//jrZ/PIfm+QYw9OCeDgbymFHChM8YVIvN2zArwsRftKgE33QfRWvQk4DPg== - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-async@*": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/rx-lite-async/-/rx-lite-async-4.0.2.tgz#27fbf0caeff029f41e2d2aae638b05e91ceb600c" - integrity sha512-vTEv5o8l6702ZwfAM5aOeVDfUwBSDOs+ARoGmWAKQ6LOInQ8J4/zjM7ov12fuTpktUKdMQjkeCp07Vd73mPkxw== - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-backpressure@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-lite-backpressure/-/rx-lite-backpressure-4.0.3.tgz#05abb19bdf87cc740196c355e5d0b37bb50b5d56" - integrity sha512-Y6aIeQCtNban5XSAF4B8dffhIKu6aAy/TXFlScHzSxh6ivfQBQw6UjxyEJxIOt3IT49YkS+siuayM2H/Q0cmgA== - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-coincidence@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-lite-coincidence/-/rx-lite-coincidence-4.0.3.tgz#80bd69acc4054a15cdc1638e2dc8843498cd85c0" - integrity sha512-1VNJqzE9gALUyMGypDXZZXzR0Tt7LC9DdAZQ3Ou/Q0MubNU35agVUNXKGHKpNTba+fr8GdIdkC26bRDqtCQBeQ== - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-experimental@*": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/rx-lite-experimental/-/rx-lite-experimental-4.0.1.tgz#c532f5cbdf3f2c15da16ded8930d1b2984023cbd" - integrity sha1-xTL1y98/LBXaFt7Ykw0bKYQCPL0= - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-joinpatterns@*": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/rx-lite-joinpatterns/-/rx-lite-joinpatterns-4.0.1.tgz#f70fe370518a8432f29158cc92ffb56b4e4afc3e" - integrity sha1-9w/jcFGKhDLykVjMkv+1a05K/D4= - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-testing@*": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/rx-lite-testing/-/rx-lite-testing-4.0.1.tgz#21b19d11f4dfd6ffef5a9d1648e9c8879bfe21e9" - integrity sha1-IbGdEfTf1v/vWp0WSOnIh5v+Iek= - dependencies: - "@types/rx-lite-virtualtime" "*" - -"@types/rx-lite-time@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-lite-time/-/rx-lite-time-4.0.3.tgz#0eda65474570237598f3448b845d2696f2dbb1c4" - integrity sha512-ukO5sPKDRwCGWRZRqPlaAU0SKVxmWwSjiOrLhoQDoWxZWg6vyB9XLEZViKOzIO6LnTIQBlk4UylYV0rnhJLxQw== - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-virtualtime@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-lite-virtualtime/-/rx-lite-virtualtime-4.0.3.tgz#4b30cacd0fe2e53af29f04f7438584c7d3959537" - integrity sha512-3uC6sGmjpOKatZSVHI2xB1+dedgml669ZRvqxy+WqmGJDVusOdyxcKfyzjW0P3/GrCiN4nmRkLVMhPwHCc5QLg== - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite@*": - version "4.0.6" - resolved "https://registry.yarnpkg.com/@types/rx-lite/-/rx-lite-4.0.6.tgz#3c02921c4244074234f26b772241bcc20c18c253" - integrity sha512-oYiDrFIcor9zDm0VDUca1UbROiMYBxMLMaM6qzz4ADAfOmA9r1dYEcAFH+2fsPI5BCCjPvV9pWC3X3flbrvs7w== - dependencies: - "@types/rx-core" "*" - "@types/rx-core-binding" "*" - -"@types/rx@*": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@types/rx/-/rx-4.1.1.tgz#598fc94a56baed975f194574e0f572fd8e627a48" - integrity sha1-WY/JSla67ZdfGUV04PVy/Y5iekg= - dependencies: - "@types/rx-core" "*" - "@types/rx-core-binding" "*" - "@types/rx-lite" "*" - "@types/rx-lite-aggregates" "*" - "@types/rx-lite-async" "*" - "@types/rx-lite-backpressure" "*" - "@types/rx-lite-coincidence" "*" - "@types/rx-lite-experimental" "*" - "@types/rx-lite-joinpatterns" "*" - "@types/rx-lite-testing" "*" - "@types/rx-lite-time" "*" - "@types/rx-lite-virtualtime" "*" - -"@types/semver@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.1.0.tgz#c8c630d4c18cd326beff77404887596f96408408" - integrity sha512-pOKLaubrAEMUItGNpgwl0HMFPrSAFic8oSVIvfu1UwcgGNmNyK9gyhBHKmBnUTwwVvpZfkzUC0GaMgnL6P86uA== - dependencies: - "@types/node" "*" - -"@types/serve-static@*": - version "1.13.3" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1" - integrity sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g== - dependencies: - "@types/express-serve-static-core" "*" - "@types/mime" "*" - -"@types/source-list-map@*": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" - integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== - -"@types/through@*": - version "0.0.30" - resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.30.tgz#e0e42ce77e897bd6aead6f6ea62aeb135b8a3895" - integrity sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg== - dependencies: - "@types/node" "*" - -"@types/webpack-sources@^0.1.5": - version "0.1.6" - resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-0.1.6.tgz#3d21dfc2ec0ad0c77758e79362426a9ba7d7cbcb" - integrity sha512-FtAWR7wR5ocJ9+nP137DV81tveD/ZgB1sadnJ/axUGM3BUVfRPx8oQNMtv3JNfTeHx3VP7cXiyfR/jmtEsVHsQ== - dependencies: - "@types/node" "*" - "@types/source-list-map" "*" - source-map "^0.6.1" - -"@webassemblyjs/ast@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359" - integrity sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ== - dependencies: - "@webassemblyjs/helper-module-context" "1.8.5" - "@webassemblyjs/helper-wasm-bytecode" "1.8.5" - "@webassemblyjs/wast-parser" "1.8.5" - -"@webassemblyjs/floating-point-hex-parser@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz#1ba926a2923613edce496fd5b02e8ce8a5f49721" - integrity sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ== - -"@webassemblyjs/helper-api-error@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz#c49dad22f645227c5edb610bdb9697f1aab721f7" - integrity sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA== - -"@webassemblyjs/helper-buffer@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz#fea93e429863dd5e4338555f42292385a653f204" - integrity sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q== - -"@webassemblyjs/helper-code-frame@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz#9a740ff48e3faa3022b1dff54423df9aa293c25e" - integrity sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ== - dependencies: - "@webassemblyjs/wast-printer" "1.8.5" - -"@webassemblyjs/helper-fsm@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz#ba0b7d3b3f7e4733da6059c9332275d860702452" - integrity sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow== - -"@webassemblyjs/helper-module-context@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz#def4b9927b0101dc8cbbd8d1edb5b7b9c82eb245" - integrity sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g== - dependencies: - "@webassemblyjs/ast" "1.8.5" - mamacro "^0.0.3" - -"@webassemblyjs/helper-wasm-bytecode@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz#537a750eddf5c1e932f3744206551c91c1b93e61" - integrity sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ== - -"@webassemblyjs/helper-wasm-section@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz#74ca6a6bcbe19e50a3b6b462847e69503e6bfcbf" - integrity sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-buffer" "1.8.5" - "@webassemblyjs/helper-wasm-bytecode" "1.8.5" - "@webassemblyjs/wasm-gen" "1.8.5" - -"@webassemblyjs/ieee754@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz#712329dbef240f36bf57bd2f7b8fb9bf4154421e" - integrity sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.8.5.tgz#044edeb34ea679f3e04cd4fd9824d5e35767ae10" - integrity sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.8.5.tgz#a8bf3b5d8ffe986c7c1e373ccbdc2a0915f0cedc" - integrity sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw== - -"@webassemblyjs/wasm-edit@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz#962da12aa5acc1c131c81c4232991c82ce56e01a" - integrity sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-buffer" "1.8.5" - "@webassemblyjs/helper-wasm-bytecode" "1.8.5" - "@webassemblyjs/helper-wasm-section" "1.8.5" - "@webassemblyjs/wasm-gen" "1.8.5" - "@webassemblyjs/wasm-opt" "1.8.5" - "@webassemblyjs/wasm-parser" "1.8.5" - "@webassemblyjs/wast-printer" "1.8.5" - -"@webassemblyjs/wasm-gen@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz#54840766c2c1002eb64ed1abe720aded714f98bc" - integrity sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-wasm-bytecode" "1.8.5" - "@webassemblyjs/ieee754" "1.8.5" - "@webassemblyjs/leb128" "1.8.5" - "@webassemblyjs/utf8" "1.8.5" - -"@webassemblyjs/wasm-opt@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz#b24d9f6ba50394af1349f510afa8ffcb8a63d264" - integrity sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-buffer" "1.8.5" - "@webassemblyjs/wasm-gen" "1.8.5" - "@webassemblyjs/wasm-parser" "1.8.5" - -"@webassemblyjs/wasm-parser@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz#21576f0ec88b91427357b8536383668ef7c66b8d" - integrity sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-api-error" "1.8.5" - "@webassemblyjs/helper-wasm-bytecode" "1.8.5" - "@webassemblyjs/ieee754" "1.8.5" - "@webassemblyjs/leb128" "1.8.5" - "@webassemblyjs/utf8" "1.8.5" - -"@webassemblyjs/wast-parser@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz#e10eecd542d0e7bd394f6827c49f3df6d4eefb8c" - integrity sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/floating-point-hex-parser" "1.8.5" - "@webassemblyjs/helper-api-error" "1.8.5" - "@webassemblyjs/helper-code-frame" "1.8.5" - "@webassemblyjs/helper-fsm" "1.8.5" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/wast-printer@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz#114bbc481fd10ca0e23b3560fa812748b0bae5bc" - integrity sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/wast-parser" "1.8.5" - "@xtuc/long" "4.2.2" - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== - -"@yarnpkg/lockfile@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" - integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== - -JSONStream@^1.0.4, JSONStream@^1.2.1, JSONStream@^1.3.4: - version "1.3.5" - resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" - integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" - -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" - integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== - dependencies: - mime-types "~2.1.24" - negotiator "0.6.2" - -acorn@^6.2.1: - version "6.4.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.0.tgz#b659d2ffbafa24baf5db1cdbb2c94a983ecd2784" - integrity sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw== - -add-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa" - integrity sha1-anmQQ3ynNtXhKI25K9MmbV9csqo= - -adm-zip@0.4.4: - version "0.4.4" - resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.4.tgz#a61ed5ae6905c3aea58b3a657d25033091052736" - integrity sha1-ph7VrmkFw66lizplfSUDMJEFJzY= - -after@0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" - integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8= - -agent-base@2: - version "2.1.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-2.1.1.tgz#d6de10d5af6132d5bd692427d46fc538539094c7" - integrity sha1-1t4Q1a9hMtW9aSQn1G/FOFOQlMc= - dependencies: - extend "~3.0.0" - semver "~5.0.1" - -agent-base@4, agent-base@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" - integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== - dependencies: - es6-promisify "^5.0.0" - -agent-base@5: - version "5.1.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" - integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== - -agent-base@6: - version "6.0.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.0.tgz#5d0101f19bbfaed39980b22ae866de153b93f09a" - integrity sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw== - dependencies: - debug "4" - -agent-base@~4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" - integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== - dependencies: - es6-promisify "^5.0.0" - -agentkeepalive@^3.4.1: - version "3.5.2" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.5.2.tgz#a113924dd3fa24a0bc3b78108c450c2abee00f67" - integrity sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ== - dependencies: - humanize-ms "^1.2.1" - -aggregate-error@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0" - integrity sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - -ajv-errors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" - integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== - -ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" - integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== - -ajv@6.10.2: - version "6.10.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" - integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw== - dependencies: - fast-deep-equal "^2.0.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@6.12.0, ajv@^6.12.0: - version "6.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7" - integrity sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^6.1.0, ajv@^6.10.2, ajv@^6.5.5: - version "6.11.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.11.0.tgz#c3607cbc8ae392d8a5a536f25b21f8e5f3f87fe9" - integrity sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^6.12.2: - version "6.12.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" - integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -align-text@^0.1.1, align-text@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" - integrity sha1-DNkKVhCT810KmSVsIrcGlDP60Rc= - dependencies: - kind-of "^3.0.2" - longest "^1.0.1" - repeat-string "^1.5.2" - -alphanum-sort@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" - integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= - -amdefine@>=0.0.4: - version "1.0.1" - resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" - integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU= - -ansi-align@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" - integrity sha1-w2rsy6VjuJzrVW82kPCx2eNUf38= - dependencies: - string-width "^2.0.0" - -ansi-align@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" - integrity sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw== - dependencies: - string-width "^3.0.0" - -ansi-colors@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== - -ansi-colors@^3.0.0: - version "3.2.4" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" - integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== - -ansi-escapes@^3.0.0, ansi-escapes@^3.1.0, ansi-escapes@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" - integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== - -ansi-escapes@^4.2.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.0.tgz#a4ce2b33d6b214b7950d8595c212f12ac9cc569d" - integrity sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg== - dependencies: - type-fest "^0.8.1" - -ansi-html@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" - integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4= - -ansi-regex@^0.2.0, ansi-regex@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-0.2.1.tgz#0d8e946967a3d8143f93e24e298525fc1b2235f9" - integrity sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk= - -ansi-regex@^2.0.0, ansi-regex@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== - -ansi-regex@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" - integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== - -ansi-styles@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.1.0.tgz#eaecbf66cd706882760b2f4691582b8f55d7a7de" - integrity sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94= - -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= - -ansi-styles@^3.2.0, ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" - integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== - dependencies: - "@types/color-name" "^1.1.1" - color-convert "^2.0.1" - -ansicolors@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" - integrity sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk= - -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - -anymatch@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" - integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -app-root-path@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a" - integrity sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA== - -append-transform@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-1.0.0.tgz#046a52ae582a228bd72f58acfbe2967c678759ab" - integrity sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw== - dependencies: - default-require-extensions "^2.0.0" - -aproba@^1.0.3, aproba@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - -archiver-utils@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2" - integrity sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw== - 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" - -archiver@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/archiver/-/archiver-3.1.1.tgz#9db7819d4daf60aec10fe86b16cb9258ced66ea0" - integrity sha512-5Hxxcig7gw5Jod/8Gq0OneVgLYET+oNHcxgWItq4TbhOzRLKNAFUb9edAftiMKXvXfCB0vbGrJdZDNq0dWMsxg== - dependencies: - archiver-utils "^2.1.0" - async "^2.6.3" - buffer-crc32 "^0.2.1" - glob "^7.1.4" - readable-stream "^3.4.0" - tar-stream "^2.1.0" - zip-stream "^2.1.2" - -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -aria-query@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-3.0.0.tgz#65b3fcc1ca1155a8c9ae64d6eee297f15d5133cc" - integrity sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w= - dependencies: - ast-types-flow "0.0.7" - commander "^2.11.0" - -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= - -array-find-index@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" - integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= - -array-flatten@1.1.1, array-flatten@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= - -array-flatten@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-3.0.0.tgz#6428ca2ee52c7b823192ec600fa3ed2f157cd541" - integrity sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA== - -array-flatten@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" - integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== - -array-ify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" - integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4= - -array-union@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" - integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= - dependencies: - array-uniq "^1.0.1" - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -array-uniq@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" - integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= - -arraybuffer.slice@~0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675" - integrity sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog== - -arrify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= - -arrify@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" - integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== - -as-array@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/as-array/-/as-array-1.0.0.tgz#28a6eeeaa5729f1f4eca2047df5e9de1abda0ed1" - integrity sha1-KKbu6qVynx9OyiBH316d4avaDtE= - dependencies: - lodash.isarguments "2.4.x" - lodash.isobject "^2.4.1" - lodash.values "^2.4.1" - -as-array@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/as-array/-/as-array-2.0.0.tgz#4f04805d87f8fce8e511bc2108f8e5e3a287d547" - integrity sha1-TwSAXYf4/OjlEbwhCPjl46KH1Uc= - -asap@^2.0.0, asap@~2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= - -ascli@~1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ascli/-/ascli-1.0.1.tgz#bcfa5974a62f18e81cabaeb49732ab4a88f906bc" - integrity sha1-vPpZdKYvGOgcq660lzKrSoj5Brw= - dependencies: - colour "~0.7.1" - optjs "~3.2.2" - -asn1.js@^4.0.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" - integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -asn1@0.1.11: - version "0.1.11" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.1.11.tgz#559be18376d08a4ec4dbe80877d27818639b2df7" - integrity sha1-VZvhg3bQik7E2+gId9J4GGObLfc= - -asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= - -assert-plus@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.1.5.tgz#ee74009413002d84cec7219c6ac811812e723160" - integrity sha1-7nQAlBMALYTOxyGcasgRgS5yMWA= - -assert@^1.1.1: - version "1.5.0" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" - integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== - dependencies: - object-assign "^4.1.1" - util "0.10.3" - -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= - -ast-types-flow@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" - integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= - -async-each@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" - integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== - -async-limiter@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" - integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== - -async@^1.3.0, async@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" - integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= - -async@^2.5.0, async@^2.6.1, async@^2.6.2, async@^2.6.3: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== - dependencies: - lodash "^4.17.14" - -async@~0.2.6: - version "0.2.10" - resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" - integrity sha1-trvgsGdLnXGXCMo43owjfLUmw9E= - -async@~0.9.0: - version "0.9.2" - resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" - integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0= - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - -at-least-node@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" - integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== - -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -autoprefixer@9.7.4, autoprefixer@^9.6.5: - version "9.7.4" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.4.tgz#f8bf3e06707d047f0641d87aee8cfb174b2a5378" - integrity sha512-g0Ya30YrMBAEZk60lp+qfX5YQllG+S5W3GYCFvyHTvhOki0AEQJLPEcIuGRsqVwLi8FvXPVtwTGhfr38hVpm0g== - dependencies: - browserslist "^4.8.3" - caniuse-lite "^1.0.30001020" - chalk "^2.4.2" - normalize-range "^0.1.2" - num2fraction "^1.2.2" - postcss "^7.0.26" - postcss-value-parser "^4.0.2" - -aws-sign2@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.5.0.tgz#c57103f7a17fc037f02d7c2e64b602ea223f7d63" - integrity sha1-xXED96F/wDfwLXwuZLYC6iI/fWM= - -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= - -aws4@^1.8.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" - integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== - -axobject-query@2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.0.2.tgz#ea187abe5b9002b377f925d8bf7d1c561adf38f9" - integrity sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww== - dependencies: - ast-types-flow "0.0.7" - -babel-code-frame@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" - integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= - dependencies: - chalk "^1.1.3" - esutils "^2.0.2" - js-tokens "^3.0.2" - -babel-core@^6.26.0, babel-core@^6.9.0: - version "6.26.3" - resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207" - integrity sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA== - dependencies: - babel-code-frame "^6.26.0" - babel-generator "^6.26.0" - babel-helpers "^6.24.1" - babel-messages "^6.23.0" - babel-register "^6.26.0" - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - convert-source-map "^1.5.1" - debug "^2.6.9" - json5 "^0.5.1" - lodash "^4.17.4" - minimatch "^3.0.4" - path-is-absolute "^1.0.1" - private "^0.1.8" - slash "^1.0.0" - source-map "^0.5.7" - -babel-generator@^6.26.0: - version "6.26.1" - resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" - integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA== - dependencies: - babel-messages "^6.23.0" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - detect-indent "^4.0.0" - jsesc "^1.3.0" - lodash "^4.17.4" - source-map "^0.5.7" - trim-right "^1.0.1" - -babel-helper-hoist-variables@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" - integrity sha1-HssnaJydJVE+rbyZFKc/VAi+enY= - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helpers@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" - integrity sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI= - dependencies: - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-loader@8.0.6: - version "8.0.6" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.6.tgz#e33bdb6f362b03f4bb141a0c21ab87c501b70dfb" - integrity sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw== - dependencies: - find-cache-dir "^2.0.0" - loader-utils "^1.0.2" - mkdirp "^0.5.1" - pify "^4.0.1" - -babel-messages@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" - integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-dynamic-import-node@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f" - integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ== - dependencies: - object.assign "^4.1.0" - -babel-plugin-transform-cjs-system-wrapper@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-cjs-system-wrapper/-/babel-plugin-transform-cjs-system-wrapper-0.3.0.tgz#f5759f29becd356faab7af52c99cde8e7bad0b21" - integrity sha1-9XWfKb7NNW+qt69SyZzejnutCyE= - dependencies: - babel-template "^6.9.0" - -babel-plugin-transform-es2015-modules-systemjs@^6.6.5: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" - integrity sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM= - dependencies: - babel-helper-hoist-variables "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-global-system-wrapper@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-global-system-wrapper/-/babel-plugin-transform-global-system-wrapper-0.0.1.tgz#afb469cec0e04689b9fe7e8b1fd280fc94a6d8f2" - integrity sha1-r7RpzsDgRom5/n6LH9KA/JSm2PI= - dependencies: - babel-template "^6.9.0" - -babel-plugin-transform-system-register@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-system-register/-/babel-plugin-transform-system-register-0.0.1.tgz#9dff40390c2763ac518f0b2ad7c5ea4f65a5be25" - integrity sha1-nf9AOQwnY6xRjwsq18XqT2WlviU= - -babel-register@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" - integrity sha1-btAhFz4vy0htestFxgCahW9kcHE= - dependencies: - babel-core "^6.26.0" - babel-runtime "^6.26.0" - core-js "^2.5.0" - home-or-tmp "^2.0.0" - lodash "^4.17.4" - mkdirp "^0.5.1" - source-map-support "^0.4.15" - -babel-runtime@^6.22.0, babel-runtime@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" - integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.11.0" - -babel-template@^6.24.1, babel-template@^6.26.0, babel-template@^6.9.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" - integrity sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI= - dependencies: - babel-runtime "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - lodash "^4.17.4" - -babel-traverse@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" - integrity sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4= - dependencies: - babel-code-frame "^6.26.0" - babel-messages "^6.23.0" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - debug "^2.6.8" - globals "^9.18.0" - invariant "^2.2.2" - lodash "^4.17.4" - -babel-types@^6.24.1, babel-types@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" - integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc= - dependencies: - babel-runtime "^6.26.0" - esutils "^2.0.2" - lodash "^4.17.4" - to-fast-properties "^1.0.3" - -babylon@^6.18.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" - integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== - -backbone@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/backbone/-/backbone-1.4.0.tgz#54db4de9df7c3811c3f032f34749a4cd27f3bd12" - integrity sha512-RLmDrRXkVdouTg38jcgHhyQ/2zjg7a8E6sz2zxfz21Hh17xDJYUHBZimVIt5fUyS8vbfpeSmTL3gUjTEvUV3qQ== - dependencies: - underscore ">=1.8.3" - -backo2@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" - integrity sha1-MasayLEpNjRj41s+u2n038+6eUc= - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -base64-arraybuffer@0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" - integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg= - -base64-js@^1.0.2, base64-js@^1.2.3, base64-js@^1.3.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" - integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== - -base64id@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6" - integrity sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY= - -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - -basic-auth-connect@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/basic-auth-connect/-/basic-auth-connect-1.0.0.tgz#fdb0b43962ca7b40456a7c2bb48fe173da2d2122" - integrity sha1-/bC0OWLKe0BFanwrtI/hc9otISI= - -basic-auth@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" - integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== - dependencies: - safe-buffer "5.1.2" - -batch@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" - integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= - -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= - dependencies: - tweetnacl "^0.14.3" - -better-assert@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" - integrity sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI= - dependencies: - callsite "1.0.0" - -big-integer@^1.6.17: - version "1.6.48" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" - integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w== - -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== - -bignumber.js@^7.0.0: - version "7.2.1" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-7.2.1.tgz#80c048759d826800807c4bfd521e50edbba57a5f" - integrity sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ== - -binary-extensions@^1.0.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" - integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== - -binary-extensions@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" - integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== - -binary@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" - integrity sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk= - dependencies: - buffers "~0.1.1" - chainsaw "~0.1.0" - -bindings@1.2.x, bindings@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.2.1.tgz#14ad6113812d2d37d72e67b4cacb4bb726505f11" - integrity sha1-FK1hE4EtLTfXLme0ystLtyZQXxE= - -bindings@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" - -bl@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88" - integrity sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A== - dependencies: - readable-stream "^3.0.1" - -bl@~0.9.0: - version "0.9.5" - resolved "https://registry.yarnpkg.com/bl/-/bl-0.9.5.tgz#c06b797af085ea00bc527afc8efcf11de2232054" - integrity sha1-wGt5evCF6gC8Unr8jvzxHeIjIFQ= - dependencies: - readable-stream "~1.0.26" - -blob@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683" - integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig== - -bluebird@2.9.6: - version "2.9.6" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.9.6.tgz#1fc3a6b1685267dc121b5ec89b32ce069d81ab7d" - integrity sha1-H8OmsWhSZ9wSG17ImzLOBp2Bq30= - -bluebird@^2.9.30: - version "2.11.0" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" - integrity sha1-U0uQM8AiyVecVro7Plpcqvu2UOE= - -bluebird@^3.3.0, bluebird@^3.3.4, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5: - version "3.7.2" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" - integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== - -bluebird@~3.4.1: - version "3.4.7" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" - integrity sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM= - -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: - version "4.11.8" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" - integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== - -body-parser@1.19.0, body-parser@^1.16.1, body-parser@^1.19.0: - version "1.19.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" - integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== - dependencies: - bytes "3.1.0" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.2" - http-errors "1.7.2" - iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.7.0" - raw-body "2.4.0" - type-is "~1.6.17" - -bonjour@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" - integrity sha1-jokKGD2O6aI5OzhExpGkK897yfU= - dependencies: - array-flatten "^2.1.0" - deep-equal "^1.0.1" - dns-equal "^1.0.0" - dns-txt "^2.0.2" - multicast-dns "^6.0.1" - multicast-dns-service-types "^1.1.0" - -boolbase@^1.0.0, boolbase@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= - -boom@2.x.x: - version "2.10.1" - resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" - integrity sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8= - dependencies: - hoek "2.x.x" - -boxen@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" - integrity sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw== - dependencies: - ansi-align "^2.0.0" - camelcase "^4.0.0" - chalk "^2.0.1" - cli-boxes "^1.0.0" - string-width "^2.0.0" - term-size "^1.2.0" - widest-line "^2.0.0" - -boxen@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" - integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ== - dependencies: - ansi-align "^3.0.0" - camelcase "^5.3.1" - chalk "^3.0.0" - cli-boxes "^2.2.0" - string-width "^4.1.0" - term-size "^2.1.0" - type-fest "^0.8.1" - widest-line "^3.1.0" - -brace-expansion@^1.0.0, brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^2.3.1, braces@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - -braces@^3.0.1, braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -brorand@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= - -browserify-aes@^1.0.0, browserify-aes@^1.0.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" - integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== - dependencies: - buffer-xor "^1.0.3" - cipher-base "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.3" - inherits "^2.0.1" - safe-buffer "^5.0.1" - -browserify-cipher@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" - integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== - dependencies: - browserify-aes "^1.0.4" - browserify-des "^1.0.0" - evp_bytestokey "^1.0.0" - -browserify-des@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" - integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== - dependencies: - cipher-base "^1.0.1" - des.js "^1.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -browserify-rsa@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" - integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= - dependencies: - bn.js "^4.1.0" - randombytes "^2.0.1" - -browserify-sign@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" - integrity sha1-qk62jl17ZYuqa/alfmMMvXqT0pg= - dependencies: - bn.js "^4.1.1" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.2" - elliptic "^6.0.0" - inherits "^2.0.1" - parse-asn1 "^5.0.0" - -browserify-zlib@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" - integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== - dependencies: - pako "~1.0.5" - -browserslist@^4.0.0, browserslist@^4.7.0, browserslist@^4.8.3: - version "4.8.6" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.8.6.tgz#96406f3f5f0755d272e27a66f4163ca821590a7e" - integrity sha512-ZHao85gf0eZ0ESxLfCp73GG9O/VTytYDIkIiZDlURppLTI9wErSM/5yAKEq6rcUdxBLjMELmrYUJGg5sxGKMHg== - dependencies: - caniuse-lite "^1.0.30001023" - electron-to-chromium "^1.3.341" - node-releases "^1.1.47" - -browserslist@^4.9.1: - version "4.11.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.11.1.tgz#92f855ee88d6e050e7e7311d987992014f1a1f1b" - integrity sha512-DCTr3kDrKEYNw6Jb9HFxVLQNaue8z+0ZfRBRjmCunKDEXEBajKDj2Y+Uelg+Pi29OnvaSGwjOsnRyNEkXzHg5g== - dependencies: - caniuse-lite "^1.0.30001038" - electron-to-chromium "^1.3.390" - node-releases "^1.1.53" - pkg-up "^2.0.0" - -buffer-alloc-unsafe@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" - integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg== - -buffer-alloc@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" - integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow== - dependencies: - buffer-alloc-unsafe "^1.1.0" - buffer-fill "^1.0.0" - -buffer-crc32@^0.2.1, buffer-crc32@^0.2.13: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= - -buffer-equal-constant-time@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= - -buffer-fill@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" - integrity sha1-+PeLdniYiO858gXNY39o5wISKyw= - -buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== - -buffer-indexof-polyfill@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.1.tgz#a9fb806ce8145d5428510ce72f278bb363a638bf" - integrity sha1-qfuAbOgUXVQoUQznLyeLs2OmOL8= - -buffer-indexof@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" - integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g== - -buffer-xor@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" - integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= - -buffer@^4.3.0: - version "4.9.2" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" - integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - isarray "^1.0.0" - -buffer@^5.1.0: - version "5.4.3" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.3.tgz#3fbc9c69eb713d323e3fc1a895eee0710c072115" - integrity sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - -buffers@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" - integrity sha1-skV5w77U1tOWru5tmorn9Ugqt7s= - -bufferutil@1.2.x: - version "1.2.1" - resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-1.2.1.tgz#37be5d36e1e06492221e68d474b1ac58e510cbd7" - integrity sha1-N75dNuHgZJIiHmjUdLGsWOUQy9c= - dependencies: - bindings "1.2.x" - nan "^2.0.5" - -bufferutil@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.1.tgz#3a177e8e5819a1243fe16b63a199951a7ad8d4a7" - integrity sha512-xowrxvpxojqkagPcWRQVXZl0YXhRhAtBEIq3VoER1NH5Mw1n1o0ojdspp+GS2J//2gCVyrzQDApQ4unGF+QOoA== - dependencies: - node-gyp-build "~3.7.0" - -builtin-modules@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" - integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= - -builtin-modules@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484" - integrity sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw== - -builtin-status-codes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" - integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= - -builtins@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" - integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og= - -bytebuffer@~5: - version "5.0.1" - resolved "https://registry.yarnpkg.com/bytebuffer/-/bytebuffer-5.0.1.tgz#582eea4b1a873b6d020a48d58df85f0bba6cfddd" - integrity sha1-WC7qSxqHO20CCkjVjfhfC7ps/d0= - dependencies: - long "~3" - -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= - -bytes@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" - integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== - -cacache@15.0.0: - version "15.0.0" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.0.tgz#133b59edbd2a37ea8ef2d54964c6f247e47e5059" - integrity sha512-L0JpXHhplbJSiDGzyJJnJCTL7er7NzbBgxzVqLswEb4bO91Zbv17OUMuUeu/q0ZwKn3V+1HM4wb9tO4eVE/K8g== - dependencies: - chownr "^1.1.2" - fs-minipass "^2.0.0" - glob "^7.1.4" - infer-owner "^1.0.4" - lru-cache "^5.1.1" - minipass "^3.1.1" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.2" - mkdirp "^1.0.3" - move-concurrently "^1.0.1" - p-map "^3.0.0" - promise-inflight "^1.0.1" - rimraf "^2.7.1" - ssri "^8.0.0" - tar "^6.0.1" - unique-filename "^1.1.1" - -cacache@^12.0.0, cacache@^12.0.2: - version "12.0.3" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.3.tgz#be99abba4e1bf5df461cd5a2c1071fc432573390" - integrity sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw== - dependencies: - bluebird "^3.5.5" - chownr "^1.1.1" - figgy-pudding "^3.5.1" - glob "^7.1.4" - graceful-fs "^4.1.15" - infer-owner "^1.0.3" - lru-cache "^5.1.1" - mississippi "^3.0.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.3" - ssri "^6.0.1" - unique-filename "^1.1.1" - y18n "^4.0.0" - -cacache@^15.0.4: - version "15.0.4" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.4.tgz#b2c23cf4ac4f5ead004fb15a0efb0a20340741f1" - integrity sha512-YlnKQqTbD/6iyoJvEY3KJftjrdBYroCbxxYXzhOzsFLWlp6KX4BOlEf4mTx0cMUfVaTS3ENL2QtDWeRYoGLkkw== - dependencies: - "@npmcli/move-file" "^1.0.1" - chownr "^2.0.0" - fs-minipass "^2.0.0" - glob "^7.1.4" - infer-owner "^1.0.4" - lru-cache "^5.1.1" - minipass "^3.1.1" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.2" - mkdirp "^1.0.3" - p-map "^4.0.0" - promise-inflight "^1.0.1" - rimraf "^3.0.2" - ssri "^8.0.0" - tar "^6.0.2" - unique-filename "^1.1.1" - -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - -cacheable-request@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" - integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^3.0.0" - lowercase-keys "^2.0.0" - normalize-url "^4.1.0" - responselike "^1.0.2" - -caller-callsite@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" - integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= - dependencies: - callsites "^2.0.0" - -caller-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" - integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= - dependencies: - caller-callsite "^2.0.0" - -callsite@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" - integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA= - -callsites@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" - integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camelcase-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" - integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc= - dependencies: - camelcase "^2.0.0" - map-obj "^1.0.0" - -camelcase-keys@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-4.2.0.tgz#a2aa5fb1af688758259c32c141426d78923b9b77" - integrity sha1-oqpfsa9oh1glnDLBQUJteJI7m3c= - dependencies: - camelcase "^4.1.0" - map-obj "^2.0.0" - quick-lru "^1.0.0" - -camelcase@^1.0.2: - version "1.2.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" - integrity sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk= - -camelcase@^2.0.0, camelcase@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" - integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= - -camelcase@^4.0.0, camelcase@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" - integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= - -camelcase@^5.0.0, camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -caniuse-api@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" - integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== - dependencies: - browserslist "^4.0.0" - caniuse-lite "^1.0.0" - lodash.memoize "^4.1.2" - lodash.uniq "^4.5.0" - -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001023: - version "1.0.30001023" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001023.tgz#b82155827f3f5009077bdd2df3d8968bcbcc6fc4" - integrity sha512-C5TDMiYG11EOhVOA62W1p3UsJ2z4DsHtMBQtjzp3ZsUglcQn62WOUgW0y795c7A5uZ+GCEIvzkMatLIlAsbNTA== - -caniuse-lite@^1.0.30001032, caniuse-lite@^1.0.30001038: - version "1.0.30001039" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001039.tgz#b3814a1c38ffeb23567f8323500c09526a577bbe" - integrity sha512-SezbWCTT34eyFoWHgx8UWso7YtvtM7oosmFoXbCkdC6qJzRfBTeTgE9REtKtiuKXuMwWTZEvdnFNGAyVMorv8Q== - -canonical-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/canonical-path/-/canonical-path-1.0.0.tgz#fcb470c23958def85081856be7a86e904f180d1d" - integrity sha512-feylzsbDxi1gPZ1IjystzIQZagYYLvfKrSuygUCgf7z6x790VEzze5QEkdSV1U58RA7Hi0+v6fv4K54atOzATg== - -capture-stack-trace@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d" - integrity sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw== - -cardinal@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-2.1.1.tgz#7cc1055d822d212954d07b085dea251cc7bc5505" - integrity sha1-fMEFXYItISlU0HsIXeolHMe8VQU= - dependencies: - ansicolors "~0.3.2" - redeyed "~2.1.0" - -caseless@~0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.10.0.tgz#ed6b2719adcd1fd18f58dc081c0f1a5b43963909" - integrity sha1-7WsnGa3NH9GPWNwIHA8aW0OWOQk= - -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= - -center-align@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" - integrity sha1-qg0yYptu6XIgBBHL1EYckHvCt60= - dependencies: - align-text "^0.1.3" - lazy-cache "^1.0.3" - -chainsaw@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" - integrity sha1-XqtQsor+WAdNDVgpE4iCi15fvJg= - dependencies: - traverse ">=0.3.0 <0.4" - -chalk@0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174" - integrity sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ= - dependencies: - ansi-styles "^1.1.0" - escape-string-regexp "^1.0.0" - has-ansi "^0.1.0" - strip-ansi "^0.3.0" - supports-color "^0.2.0" - -chalk@^1.0.0, chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chalk@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" - integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -char-spinner@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/char-spinner/-/char-spinner-1.0.1.tgz#e6ea67bd247e107112983b7ab0479ed362800081" - integrity sha1-5upnvSR+EHESmDt6sEee02KAAIE= - -chardet@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" - integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== - -"chokidar@>=2.0.0 <4.0.0", chokidar@^3.0.0, chokidar@^3.0.2, chokidar@^3.2.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" - integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg== - dependencies: - anymatch "~3.1.1" - braces "~3.0.2" - glob-parent "~5.1.0" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.3.0" - optionalDependencies: - fsevents "~2.1.2" - -chokidar@^2.0.2, chokidar@^2.1.8: - version "2.1.8" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" - integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== - dependencies: - anymatch "^2.0.0" - async-each "^1.0.1" - braces "^2.3.2" - glob-parent "^3.1.0" - inherits "^2.0.3" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - normalize-path "^3.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.2.1" - upath "^1.1.1" - optionalDependencies: - fsevents "^1.2.7" - -chownr@^1.1.1, chownr@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" - integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw== - -chownr@^1.1.3: - version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - -chownr@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" - integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== - -chrome-trace-event@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" - integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== - dependencies: - tslib "^1.9.0" - -ci-info@^1.5.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" - integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A== - -ci-info@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== - -cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" - integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -circular-dependency-plugin@5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/circular-dependency-plugin/-/circular-dependency-plugin-5.2.0.tgz#e09dbc2dd3e2928442403e2d45b41cea06bc0a93" - integrity sha512-7p4Kn/gffhQaavNfyDFg7LS5S/UT1JAjyGd4UqR2+jzoYF02eDkj0Ec3+48TsIa4zghjLY87nQHIh/ecK9qLdw== - -cjson@^0.3.1: - version "0.3.3" - resolved "https://registry.yarnpkg.com/cjson/-/cjson-0.3.3.tgz#a92d9c786e5bf9b930806329ee05d5d3261b4afa" - integrity sha1-qS2ceG5b+bkwgGMp7gXV0yYbSvo= - dependencies: - json-parse-helpfulerror "^1.0.3" - -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== - -cli-boxes@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" - integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM= - -cli-boxes@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d" - integrity sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w== - -cli-color@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-1.4.0.tgz#7d10738f48526824f8fe7da51857cb0f572fe01f" - integrity sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w== - dependencies: - ansi-regex "^2.1.1" - d "1" - es5-ext "^0.10.46" - es6-iterator "^2.0.3" - memoizee "^0.4.14" - timers-ext "^0.1.5" - -cli-cursor@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" - integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= - dependencies: - restore-cursor "^2.0.0" - -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - -cli-spinners@^2.0.0, cli-spinners@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.2.0.tgz#e8b988d9206c692302d8ee834e7a85c0144d8f77" - integrity sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ== - -cli-table@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23" - integrity sha1-9TsFJmqLGguTSz0IIebi3FkUriM= - dependencies: - colors "1.0.3" - -cli-width@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" - integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= - -cliui@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" - integrity sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE= - dependencies: - center-align "^0.1.1" - right-align "^0.1.1" - wordwrap "0.0.2" - -cliui@^3.0.3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" - integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi "^2.0.0" - -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" - integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== - dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" - -cliui@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" - integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^6.2.0" - -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - -clone-response@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" - integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= - dependencies: - mimic-response "^1.0.0" - -clone@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" - integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= - -clone@^2.1.1, clone@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" - integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= - -coa@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" - integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== - dependencies: - "@types/q" "^1.5.1" - chalk "^2.4.1" - q "^1.1.2" - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - -codelyzer@^5.0.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/codelyzer/-/codelyzer-5.2.1.tgz#44fd431e128009f38c761828c33ebacba9549d32" - integrity sha512-awBZXFcJUyC5HMYXiHzjr3D24tww2l1D1OqtfA9vUhEtYr32a65A+Gblm/OvsO+HuKLYzn8EDMw1inSM3VbxWA== - dependencies: - app-root-path "^2.2.1" - aria-query "^3.0.0" - axobject-query "2.0.2" - css-selector-tokenizer "^0.7.1" - cssauron "^1.4.0" - damerau-levenshtein "^1.0.4" - semver-dsl "^1.0.1" - source-map "^0.5.7" - sprintf-js "^1.1.2" - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - -color-convert@^1.9.0, color-convert@^1.9.1: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - -color-name@^1.0.0, color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -color-string@^1.5.2: - version "1.5.3" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" - integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color@3.0.x: - version "3.0.0" - resolved "https://registry.yarnpkg.com/color/-/color-3.0.0.tgz#d920b4328d534a3ac8295d68f7bd4ba6c427be9a" - integrity sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w== - dependencies: - color-convert "^1.9.1" - color-string "^1.5.2" - -color@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/color/-/color-3.1.2.tgz#68148e7f85d41ad7649c5fa8c8106f098d229e10" - integrity sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg== - dependencies: - color-convert "^1.9.1" - color-string "^1.5.2" - -colornames@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/colornames/-/colornames-1.1.1.tgz#f8889030685c7c4ff9e2a559f5077eb76a816f96" - integrity sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y= - -colors@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" - integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= - -colors@^1.1.0, colors@^1.2.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" - integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== - -colorspace@1.1.x: - version "1.1.2" - resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.2.tgz#e0128950d082b86a2168580796a0aa5d6c68d8c5" - integrity sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ== - dependencies: - color "3.0.x" - text-hex "1.0.x" - -colour@~0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/colour/-/colour-0.7.1.tgz#9cb169917ec5d12c0736d3e8685746df1cadf778" - integrity sha1-nLFpkX7F0SwHNtPoaFdG3xyt93g= - -combined-stream@^1.0.6, combined-stream@~1.0.1, combined-stream@~1.0.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -combined-stream@~0.0.4: - version "0.0.7" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-0.0.7.tgz#0137e657baa5a7541c57ac37ac5fc07d73b4dc1f" - integrity sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8= - dependencies: - delayed-stream "0.0.5" - -commander@2.6, commander@2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d" - integrity sha1-nfflL7Kgyw+4kFjugMMQQiXzfh0= - -commander@2.9.x: - version "2.9.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" - integrity sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q= - dependencies: - graceful-readlink ">= 1.0.0" - -commander@^2.11.0, commander@^2.12.1, commander@^2.20.0, commander@^2.8.1, commander@~2.20.3: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -commander@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.0.tgz#545983a0603fe425bc672d66c9e3c89c42121a83" - integrity sha512-NIQrwvv9V39FHgGFm36+U9SMQzbiHvU79k+iADraJTpmrFFfx7Ds0IvDoAdZsDrknlkRk14OYoWXb57uTh7/sw== - -commander@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" - integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== - -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= - -compare-func@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-1.3.2.tgz#99dd0ba457e1f9bc722b12c08ec33eeab31fa648" - integrity sha1-md0LpFfh+bxyKxLAjsM+6rMfpkg= - dependencies: - array-ify "^1.0.0" - dot-prop "^3.0.0" - -compare-semver@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/compare-semver/-/compare-semver-1.1.0.tgz#7c0a79a27bb80b6c6994445f82958259d3d02153" - integrity sha1-fAp5onu4C2xplERfgpWCWdPQIVM= - dependencies: - semver "^5.0.1" - -compare-versions@^3.4.0: - version "3.5.1" - resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.5.1.tgz#26e1f5cf0d48a77eced5046b9f67b6b61075a393" - integrity sha512-9fGPIB7C6AyM18CJJBHt5EnCZDG3oiTJYy0NjfIAGjKpzv0tkxWko7TNQHF5ymqm7IH03tqmeuBxtvD+Izh6mg== - -compare-versions@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" - integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== - -component-bind@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" - integrity sha1-AMYIq33Nk4l8AAllGx06jh5zu9E= - -component-emitter@1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" - integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= - -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - -component-inherit@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" - integrity sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM= - -compress-commons@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-2.1.1.tgz#9410d9a534cf8435e3fbbb7c6ce48de2dc2f0610" - integrity sha512-eVw6n7CnEMFzc3duyFVrQEuY1BlHR3rYsSztyG32ibGMW722i3C6IizEGMFmfMU+A+fALvBIwxN3czffTcdA+Q== - dependencies: - buffer-crc32 "^0.2.13" - crc32-stream "^3.0.1" - normalize-path "^3.0.0" - readable-stream "^2.3.6" - -compressible@^2.0.12, compressible@~2.0.16: - version "2.0.18" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" - integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== - dependencies: - mime-db ">= 1.43.0 < 2" - -compression@^1.7.0, compression@^1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" - integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== - dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" - debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" - vary "~1.1.2" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -concat-stream@^1.5.0: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - -concat-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" - integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.0.2" - typedarray "^0.0.6" - -concurrently@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-2.2.0.tgz#bad248e0bb129fb1621768903a6311d45d56895a" - integrity sha1-utJI4LsSn7FiF2iQOmMR1F1WiVo= - dependencies: - bluebird "2.9.6" - chalk "0.5.1" - commander "2.6.0" - cross-spawn "^0.2.9" - lodash "^4.5.1" - moment "^2.11.2" - rx "2.3.24" - -configstore@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f" - integrity sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw== - dependencies: - dot-prop "^4.1.0" - graceful-fs "^4.1.2" - make-dir "^1.0.0" - unique-string "^1.0.0" - write-file-atomic "^2.0.0" - xdg-basedir "^3.0.0" - -configstore@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.0.tgz#37de662c7a49b5fe8dbcf8f6f5818d2d81ed852b" - integrity sha512-eE/hvMs7qw7DlcB5JPRnthmrITuHMmACUJAp89v6PT6iOqzoLS7HRWhBtuHMlhNHo2AhUSA/3Dh1bKNJHcublQ== - dependencies: - dot-prop "^5.1.0" - graceful-fs "^4.1.2" - make-dir "^3.0.0" - unique-string "^2.0.0" - write-file-atomic "^3.0.0" - xdg-basedir "^4.0.0" - -configstore@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" - integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== - dependencies: - dot-prop "^5.2.0" - graceful-fs "^4.1.2" - make-dir "^3.0.0" - unique-string "^2.0.0" - write-file-atomic "^3.0.0" - xdg-basedir "^4.0.0" - -connect-history-api-fallback@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" - integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== - -connect-query@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/connect-query/-/connect-query-1.0.0.tgz#de44f577209da2404d1fc04692d1a4118e582119" - integrity sha1-3kT1dyCdokBNH8BGktGkEY5YIRk= - dependencies: - qs "~6.4.0" - -connect@^3.6.0, connect@^3.6.2: - version "3.7.0" - resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8" - integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ== - dependencies: - debug "2.6.9" - finalhandler "1.1.2" - parseurl "~1.3.3" - utils-merge "1.0.1" - -console-browserify@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" - integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== - -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= - -constants-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" - integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= - -content-disposition@0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" - integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== - dependencies: - safe-buffer "5.1.2" - -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== - -conventional-changelog-angular@^1.6.6: - version "1.6.6" - resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-1.6.6.tgz#b27f2b315c16d0a1f23eb181309d0e6a4698ea0f" - integrity sha512-suQnFSqCxRwyBxY68pYTsFkG0taIdinHLNEAX5ivtw8bCRnIgnpvcHmlR/yjUyZIrNPYAoXlY1WiEKWgSE4BNg== - dependencies: - compare-func "^1.3.1" - q "^1.5.1" - -conventional-changelog-atom@^0.2.8: - version "0.2.8" - resolved "https://registry.yarnpkg.com/conventional-changelog-atom/-/conventional-changelog-atom-0.2.8.tgz#8037693455990e3256f297320a45fa47ee553a14" - integrity sha512-8pPZqhMbrnltNBizjoDCb/Sz85KyUXNDQxuAEYAU5V/eHn0okMBVjqc8aHWYpHrytyZWvMGbayOlDv7i8kEf6g== - dependencies: - q "^1.5.1" - -conventional-changelog-cli@^1.2.0: - version "1.3.22" - resolved "https://registry.yarnpkg.com/conventional-changelog-cli/-/conventional-changelog-cli-1.3.22.tgz#13570fe1728f56f013ff7a88878ff49d5162a405" - integrity sha512-pnjdIJbxjkZ5VdAX/H1wndr1G10CY8MuZgnXuJhIHglOXfIrXygb7KZC836GW9uo1u8PjEIvIw/bKX0lOmOzZg== - dependencies: - add-stream "^1.0.0" - conventional-changelog "^1.1.24" - lodash "^4.2.1" - meow "^4.0.0" - tempfile "^1.1.1" - -conventional-changelog-codemirror@^0.3.8: - version "0.3.8" - resolved "https://registry.yarnpkg.com/conventional-changelog-codemirror/-/conventional-changelog-codemirror-0.3.8.tgz#a1982c8291f4ee4d6f2f62817c6b2ecd2c4b7b47" - integrity sha512-3HFZKtBXTaUCHvz7ai6nk2+psRIkldDoNzCsom0egDtVmPsvvHZkzjynhdQyULfacRSsBTaiQ0ol6nBOL4dDiQ== - dependencies: - q "^1.5.1" - -conventional-changelog-core@^2.0.11: - version "2.0.11" - resolved "https://registry.yarnpkg.com/conventional-changelog-core/-/conventional-changelog-core-2.0.11.tgz#19b5fbd55a9697773ed6661f4e32030ed7e30287" - integrity sha512-HvTE6RlqeEZ/NFPtQeFLsIDOLrGP3bXYr7lFLMhCVsbduF1MXIe8OODkwMFyo1i9ku9NWBwVnVn0jDmIFXjDRg== - dependencies: - conventional-changelog-writer "^3.0.9" - conventional-commits-parser "^2.1.7" - dateformat "^3.0.0" - get-pkg-repo "^1.0.0" - git-raw-commits "^1.3.6" - git-remote-origin-url "^2.0.0" - git-semver-tags "^1.3.6" - lodash "^4.2.1" - normalize-package-data "^2.3.5" - q "^1.5.1" - read-pkg "^1.1.0" - read-pkg-up "^1.0.1" - through2 "^2.0.0" - -conventional-changelog-ember@^0.3.12: - version "0.3.12" - resolved "https://registry.yarnpkg.com/conventional-changelog-ember/-/conventional-changelog-ember-0.3.12.tgz#b7d31851756d0fcb49b031dffeb6afa93b202400" - integrity sha512-mmJzA7uzbrOqeF89dMMi6z17O07ORTXlTMArnLG9ZTX4oLaKNolUlxFUFlFm9JUoVWajVpaHQWjxH1EOQ+ARoQ== - dependencies: - q "^1.5.1" - -conventional-changelog-eslint@^1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/conventional-changelog-eslint/-/conventional-changelog-eslint-1.0.9.tgz#b13cc7e4b472c819450ede031ff1a75c0e3d07d3" - integrity sha512-h87nfVh2fdk9fJIvz26wCBsbDC/KxqCc5wSlNMZbXcARtbgNbNDIF7Y7ctokFdnxkzVdaHsbINkh548T9eBA7Q== - dependencies: - q "^1.5.1" - -conventional-changelog-express@^0.3.6: - version "0.3.6" - resolved "https://registry.yarnpkg.com/conventional-changelog-express/-/conventional-changelog-express-0.3.6.tgz#4a6295cb11785059fb09202180d0e59c358b9c2c" - integrity sha512-3iWVtBJZ9RnRnZveNDzOD8QRn6g6vUif0qVTWWyi5nUIAbuN1FfPVyKdAlJJfp5Im+dE8Kiy/d2SpaX/0X678Q== - dependencies: - q "^1.5.1" - -conventional-changelog-jquery@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/conventional-changelog-jquery/-/conventional-changelog-jquery-0.1.0.tgz#0208397162e3846986e71273b6c79c5b5f80f510" - integrity sha1-Agg5cWLjhGmG5xJztsecW1+A9RA= - dependencies: - q "^1.4.1" - -conventional-changelog-jscs@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/conventional-changelog-jscs/-/conventional-changelog-jscs-0.1.0.tgz#0479eb443cc7d72c58bf0bcf0ef1d444a92f0e5c" - integrity sha1-BHnrRDzH1yxYvwvPDvHURKkvDlw= - dependencies: - q "^1.4.1" - -conventional-changelog-jshint@^0.3.8: - version "0.3.8" - resolved "https://registry.yarnpkg.com/conventional-changelog-jshint/-/conventional-changelog-jshint-0.3.8.tgz#9051c1ac0767abaf62a31f74d2fe8790e8acc6c8" - integrity sha512-hn9QU4ZI/5V50wKPJNPGT4gEWgiBFpV6adieILW4MaUFynuDYOvQ71EMSj3EznJyKi/KzuXpc9dGmX8njZMjig== - dependencies: - compare-func "^1.3.1" - q "^1.5.1" - -conventional-changelog-preset-loader@^1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-1.1.8.tgz#40bb0f142cd27d16839ec6c74ee8db418099b373" - integrity sha512-MkksM4G4YdrMlT2MbTsV2F6LXu/hZR0Tc/yenRrDIKRwBl/SP7ER4ZDlglqJsCzLJi4UonBc52Bkm5hzrOVCcw== - -conventional-changelog-writer@^3.0.9: - version "3.0.9" - resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-3.0.9.tgz#4aecdfef33ff2a53bb0cf3b8071ce21f0e994634" - integrity sha512-n9KbsxlJxRQsUnK6wIBRnARacvNnN4C/nxnxCkH+B/R1JS2Fa+DiP1dU4I59mEDEjgnFaN2+9wr1P1s7GYB5/Q== - dependencies: - compare-func "^1.3.1" - conventional-commits-filter "^1.1.6" - dateformat "^3.0.0" - handlebars "^4.0.2" - json-stringify-safe "^5.0.1" - lodash "^4.2.1" - meow "^4.0.0" - semver "^5.5.0" - split "^1.0.0" - through2 "^2.0.0" - -conventional-changelog@^1.1.24: - version "1.1.24" - resolved "https://registry.yarnpkg.com/conventional-changelog/-/conventional-changelog-1.1.24.tgz#3d94c29c960f5261c002678315b756cdd3d7d1f0" - integrity sha512-2WcSUst4Y3Z4hHvoMTWXMJr/DmgVdLiMOVY1Kak2LfFz+GIz2KDp5naqbFesYbfXPmaZ5p491dO0FWZIJoJw1Q== - dependencies: - conventional-changelog-angular "^1.6.6" - conventional-changelog-atom "^0.2.8" - conventional-changelog-codemirror "^0.3.8" - conventional-changelog-core "^2.0.11" - conventional-changelog-ember "^0.3.12" - conventional-changelog-eslint "^1.0.9" - conventional-changelog-express "^0.3.6" - conventional-changelog-jquery "^0.1.0" - conventional-changelog-jscs "^0.1.0" - conventional-changelog-jshint "^0.3.8" - conventional-changelog-preset-loader "^1.1.8" - -conventional-commits-filter@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/conventional-commits-filter/-/conventional-commits-filter-1.1.6.tgz#4389cd8e58fe89750c0b5fb58f1d7f0cc8ad3831" - integrity sha512-KcDgtCRKJCQhyk6VLT7zR+ZOyCnerfemE/CsR3iQpzRRFbLEs0Y6rwk3mpDvtOh04X223z+1xyJ582Stfct/0Q== - dependencies: - is-subset "^0.1.1" - modify-values "^1.0.0" - -conventional-commits-parser@^2.1.7: - version "2.1.7" - resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-2.1.7.tgz#eca45ed6140d72ba9722ee4132674d639e644e8e" - integrity sha512-BoMaddIEJ6B4QVMSDu9IkVImlGOSGA1I2BQyOZHeLQ6qVOJLcLKn97+fL6dGbzWEiqDzfH4OkcveULmeq2MHFQ== - dependencies: - JSONStream "^1.0.4" - is-text-path "^1.0.0" - lodash "^4.2.1" - meow "^4.0.0" - split2 "^2.0.0" - through2 "^2.0.0" - trim-off-newlines "^1.0.0" - -convert-source-map@^1.5.1, convert-source-map@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" - integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== - dependencies: - safe-buffer "~5.1.1" - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= - -cookie@0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" - integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= - -cookie@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" - integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== - -copy-concurrently@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" - integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A== - dependencies: - aproba "^1.1.1" - fs-write-stream-atomic "^1.0.8" - iferr "^0.1.5" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.0" - -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= - -copy-webpack-plugin@6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-6.0.2.tgz#10efc6ad219a61acbf2f5fb50af83da38431bc34" - integrity sha512-9Gm8X0c6eXlKnmltMPFCBeGOKjtcRIyTt4VaO3k1TkNgVTe5Ov2lYsYVuyLp0kp8DItO3apewflM+1GYgh6V2Q== - dependencies: - cacache "^15.0.4" - fast-glob "^3.2.2" - find-cache-dir "^3.3.1" - glob-parent "^5.1.1" - globby "^11.0.1" - loader-utils "^2.0.0" - normalize-path "^3.0.0" - p-limit "^2.3.0" - schema-utils "^2.7.0" - serialize-javascript "^3.1.0" - webpack-sources "^1.4.3" - -core-js-compat@^3.6.2: - version "3.6.4" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.4.tgz#938476569ebb6cda80d339bcf199fae4f16fff17" - integrity sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA== - dependencies: - browserslist "^4.8.3" - semver "7.0.0" - -core-js@3.6.4: - version "3.6.4" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.4.tgz#440a83536b458114b9cb2ac1580ba377dc470647" - integrity sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw== - -core-js@^2.4.0, core-js@^2.5.0: - version "2.6.11" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" - integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== - -core-util-is@1.0.2, core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -cors@^2.8.5: - version "2.8.5" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" - integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== - dependencies: - object-assign "^4" - vary "^1" - -cosmiconfig@^5.0.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" - integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== - dependencies: - import-fresh "^2.0.0" - is-directory "^0.3.1" - js-yaml "^3.13.1" - parse-json "^4.0.0" - -cosmiconfig@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" - integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== - dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.1.0" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.7.2" - -crc32-stream@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-3.0.1.tgz#cae6eeed003b0e44d739d279de5ae63b171b4e85" - integrity sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w== - dependencies: - crc "^3.4.4" - readable-stream "^3.4.0" - -crc@^3.4.4: - version "3.8.0" - resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" - integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ== - dependencies: - buffer "^5.1.0" - -create-ecdh@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" - integrity sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw== - dependencies: - bn.js "^4.1.0" - elliptic "^6.0.0" - -create-error-class@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" - integrity sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y= - dependencies: - capture-stack-trace "^1.0.0" - -create-hash@^1.1.0, create-hash@^1.1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" - integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== - dependencies: - cipher-base "^1.0.1" - inherits "^2.0.1" - md5.js "^1.3.4" - ripemd160 "^2.0.1" - sha.js "^2.4.0" - -create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: - version "1.1.7" - resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" - integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== - dependencies: - cipher-base "^1.0.3" - create-hash "^1.1.0" - inherits "^2.0.1" - ripemd160 "^2.0.0" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -cross-env@^5.1.3: - version "5.2.1" - resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.2.1.tgz#b2c76c1ca7add66dc874d11798466094f551b34d" - integrity sha512-1yHhtcfAd1r4nwQgknowuUNfIT9E8dOMMspC36g45dN+iD1blloi7xp8X/xAIDnjHWyt1uQ8PHk2fkNaym7soQ== - dependencies: - cross-spawn "^6.0.5" - -cross-spawn@^0.2.9: - version "0.2.9" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-0.2.9.tgz#bd67f96c07efb6303b7fe94c1e979f88478e0a39" - integrity sha1-vWf5bAfvtjA7f+lMHpefiEeOCjk= - dependencies: - lru-cache "^2.5.0" - -cross-spawn@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41" - integrity sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE= - dependencies: - lru-cache "^4.0.1" - which "^1.2.9" - -cross-spawn@^5.0.1: - version "5.1.0" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" - integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= - dependencies: - lru-cache "^4.0.1" - shebang-command "^1.2.0" - which "^1.2.9" - -cross-spawn@^6.0.0, cross-spawn@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - -cryptiles@2.x.x: - version "2.0.5" - resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" - integrity sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g= - dependencies: - boom "2.x.x" - -crypto-browserify@^3.11.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" - integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== - dependencies: - browserify-cipher "^1.0.0" - browserify-sign "^4.0.0" - create-ecdh "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.0" - diffie-hellman "^5.0.0" - inherits "^2.0.1" - pbkdf2 "^3.0.3" - public-encrypt "^4.0.0" - randombytes "^2.0.0" - randomfill "^1.0.3" - -crypto-random-string@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" - integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4= - -crypto-random-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" - integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== - -css-color-names@0.0.4, css-color-names@^0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" - integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= - -css-declaration-sorter@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz#c198940f63a76d7e36c1e71018b001721054cb22" - integrity sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA== - dependencies: - postcss "^7.0.1" - timsort "^0.3.0" - -css-loader@3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.5.1.tgz#db2b2336f4169edb68e6a829ad4fd36552647b77" - integrity sha512-0G4CbcZzQ9D1Q6ndOfjFuMDo8uLYMu5vc9Abs5ztyHcKvmil6GJrMiNjzzi3tQvUF+mVRuDg7bE6Oc0Prolgig== - dependencies: - camelcase "^5.3.1" - cssesc "^3.0.0" - icss-utils "^4.1.1" - loader-utils "^1.2.3" - normalize-path "^3.0.0" - postcss "^7.0.27" - postcss-modules-extract-imports "^2.0.0" - postcss-modules-local-by-default "^3.0.2" - postcss-modules-scope "^2.2.0" - postcss-modules-values "^3.0.0" - postcss-value-parser "^4.0.3" - schema-utils "^2.6.5" - semver "^6.3.0" - -css-parse@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/css-parse/-/css-parse-2.0.0.tgz#a468ee667c16d81ccf05c58c38d2a97c780dbfd4" - integrity sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q= - dependencies: - css "^2.0.0" - -css-select-base-adapter@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" - integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== - -css-select@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" - integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== - dependencies: - boolbase "^1.0.0" - css-what "^3.2.1" - domutils "^1.7.0" - nth-check "^1.0.2" - -css-selector-tokenizer@^0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz#a177271a8bca5019172f4f891fc6eed9cbf68d5d" - integrity sha512-xYL0AMZJ4gFzJQsHUKa5jiWWi2vH77WVNg7JYRyewwj6oPh4yb/y6Y9ZCw9dsj/9UauMhtuxR+ogQd//EdEVNA== - dependencies: - cssesc "^0.1.0" - fastparse "^1.1.1" - regexpu-core "^1.0.0" - -css-tree@1.0.0-alpha.37: - version "1.0.0-alpha.37" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" - integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== - dependencies: - mdn-data "2.0.4" - source-map "^0.6.1" - -css-unit-converter@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.1.tgz#d9b9281adcfd8ced935bdbaba83786897f64e996" - integrity sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY= - -css-what@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.2.1.tgz#f4a8f12421064621b456755e34a03a2c22df5da1" - integrity sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw== - -css@^2.0.0: - version "2.2.4" - resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" - integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw== - dependencies: - inherits "^2.0.3" - source-map "^0.6.1" - source-map-resolve "^0.5.2" - urix "^0.1.0" - -cssauron@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/cssauron/-/cssauron-1.4.0.tgz#a6602dff7e04a8306dc0db9a551e92e8b5662ad8" - integrity sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg= - dependencies: - through X.X.X - -cssesc@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4" - integrity sha1-yBSQPkViM3GgR3tAEJqq++6t27Q= - -cssesc@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703" - integrity sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg== - -cssesc@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== - -cssnano-preset-default@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76" - integrity sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA== - dependencies: - css-declaration-sorter "^4.0.1" - cssnano-util-raw-cache "^4.0.1" - postcss "^7.0.0" - postcss-calc "^7.0.1" - postcss-colormin "^4.0.3" - postcss-convert-values "^4.0.1" - postcss-discard-comments "^4.0.2" - postcss-discard-duplicates "^4.0.2" - postcss-discard-empty "^4.0.1" - postcss-discard-overridden "^4.0.1" - postcss-merge-longhand "^4.0.11" - postcss-merge-rules "^4.0.3" - postcss-minify-font-values "^4.0.2" - postcss-minify-gradients "^4.0.2" - postcss-minify-params "^4.0.2" - postcss-minify-selectors "^4.0.2" - postcss-normalize-charset "^4.0.1" - postcss-normalize-display-values "^4.0.2" - postcss-normalize-positions "^4.0.2" - postcss-normalize-repeat-style "^4.0.2" - postcss-normalize-string "^4.0.2" - postcss-normalize-timing-functions "^4.0.2" - postcss-normalize-unicode "^4.0.1" - postcss-normalize-url "^4.0.1" - postcss-normalize-whitespace "^4.0.2" - postcss-ordered-values "^4.1.2" - postcss-reduce-initial "^4.0.3" - postcss-reduce-transforms "^4.0.2" - postcss-svgo "^4.0.2" - postcss-unique-selectors "^4.0.1" - -cssnano-util-get-arguments@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz#ed3a08299f21d75741b20f3b81f194ed49cc150f" - integrity sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8= - -cssnano-util-get-match@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz#c0e4ca07f5386bb17ec5e52250b4f5961365156d" - integrity sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0= - -cssnano-util-raw-cache@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz#b26d5fd5f72a11dfe7a7846fb4c67260f96bf282" - integrity sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA== - dependencies: - postcss "^7.0.0" - -cssnano-util-same-parent@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" - integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== - -cssnano@4.1.10: - version "4.1.10" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2" - integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ== - dependencies: - cosmiconfig "^5.0.0" - cssnano-preset-default "^4.0.7" - is-resolvable "^1.0.0" - postcss "^7.0.0" - -csso@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.2.tgz#e5f81ab3a56b8eefb7f0092ce7279329f454de3d" - integrity sha512-kS7/oeNVXkHWxby5tHVxlhjizRCSv8QdU7hB2FpdAibDU8FjTAolhNjKNTiLzXtUrKT6HwClE81yXwEk1309wg== - dependencies: - css-tree "1.0.0-alpha.37" - -csv-streamify@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/csv-streamify/-/csv-streamify-3.0.4.tgz#4cb614c57e3f299cca17b63fdcb4ad167777f47a" - integrity sha1-TLYUxX4/KZzKF7Y/3LStFnd39Ho= - dependencies: - through2 "2.0.1" - -ctype@0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/ctype/-/ctype-0.5.3.tgz#82c18c2461f74114ef16c135224ad0b9144ca12f" - integrity sha1-gsGMJGH3QRTvFsE1IkrQuRRMoS8= - -cuint@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b" - integrity sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs= - -currently-unhandled@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" - integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= - dependencies: - array-find-index "^1.0.1" - -custom-event@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" - integrity sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU= - -cyclist@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" - integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= - -d@1, d@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" - integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== - dependencies: - es5-ext "^0.10.50" - type "^1.0.1" - -damerau-levenshtein@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz#143c1641cb3d85c60c32329e26899adea8701791" - integrity sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug== - -dargs@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/dargs/-/dargs-4.1.0.tgz#03a9dbb4b5c2f139bf14ae53f0b8a2a6a86f4e17" - integrity sha1-A6nbtLXC8Tm/FK5T8LiipqhvThc= - dependencies: - number-is-nan "^1.0.0" - -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= - dependencies: - assert-plus "^1.0.0" - -data-uri-to-buffer@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-0.0.4.tgz#46e13ab9da8e309745c8d01ce547213ebdb2fe3f" - integrity sha1-RuE6udqOMJdFyNAc5UchPr2y/j8= - -date-and-time@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/date-and-time/-/date-and-time-0.12.0.tgz#6d30c91c47fa72edadd628b71ec2ac46909b9267" - integrity sha512-n2RJIAp93AucgF/U/Rz5WRS2Hjg5Z+QxscaaMCi6pVZT1JpJKRH+C08vyH/lRR1kxNXnPxgo3lWfd+jCb/UcuQ== - -date-format@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/date-format/-/date-format-2.1.0.tgz#31d5b5ea211cf5fd764cd38baf9d033df7e125cf" - integrity sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA== - -dateformat@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" - integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== - -debug@2, debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@3.1.0, debug@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - -debug@4, debug@4.1.1, debug@^4.1.0, debug@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== - dependencies: - ms "^2.1.1" - -debug@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.0.tgz#373687bffa678b38b1cd91f861b63850035ddc87" - integrity sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg== - dependencies: - ms "^2.1.1" - -debug@^3.0.0, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - -debuglog@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" - integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= - -decamelize-keys@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" - integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= - dependencies: - decamelize "^1.1.0" - map-obj "^1.0.0" - -decamelize@^1.0.0, decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= - -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= - -decompress-response@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" - integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= - dependencies: - mimic-response "^1.0.0" - -deep-equal@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" - integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== - dependencies: - is-arguments "^1.0.4" - is-date-object "^1.0.1" - is-regex "^1.0.4" - object-is "^1.0.1" - object-keys "^1.1.1" - regexp.prototype.flags "^1.2.0" - -deep-equal@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.0.1.tgz#fc12bbd6850e93212f21344748682ccc5a8813cf" - integrity sha512-7Et6r6XfNW61CPPCIYfm1YPGSmh6+CliYeL4km7GWJcpX5LTAflGF8drLLR+MZX+2P3NZfAfSduutBbSWqER4g== - dependencies: - es-abstract "^1.16.3" - es-get-iterator "^1.0.1" - is-arguments "^1.0.4" - is-date-object "^1.0.1" - is-regex "^1.0.4" - isarray "^2.0.5" - object-is "^1.0.1" - object-keys "^1.1.1" - regexp.prototype.flags "^1.2.0" - side-channel "^1.0.1" - which-boxed-primitive "^1.0.1" - which-collection "^1.0.0" - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - -deep-freeze@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/deep-freeze/-/deep-freeze-0.0.1.tgz#3a0b0005de18672819dfd38cd31f91179c893e84" - integrity sha1-OgsABd4YZygZ39OM0x+RF5yJPoQ= - -deep-is@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= - -deepmerge@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" - integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== - -default-gateway@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" - integrity sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA== - dependencies: - execa "^1.0.0" - ip-regex "^2.1.0" - -default-require-extensions@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-2.0.0.tgz#f5f8fbb18a7d6d50b21f641f649ebb522cfe24f7" - integrity sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc= - dependencies: - strip-bom "^3.0.0" - -defaults@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" - integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= - dependencies: - clone "^1.0.2" - -defer-to-connect@^1.0.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" - integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== - -define-properties@^1.1.2, define-properties@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" - integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== - dependencies: - object-keys "^1.0.12" - -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - -del@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" - integrity sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ== - dependencies: - "@types/glob" "^7.1.1" - globby "^6.1.0" - is-path-cwd "^2.0.0" - is-path-in-cwd "^2.0.0" - p-map "^2.0.0" - pify "^4.0.1" - rimraf "^2.6.3" - -delayed-stream@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-0.0.5.tgz#d4b1f43a93e8296dfe02694f4680bc37a313c73f" - integrity sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8= - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= - -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= - -dependency-graph@^0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/dependency-graph/-/dependency-graph-0.7.2.tgz#91db9de6eb72699209d88aea4c1fd5221cac1c49" - integrity sha512-KqtH4/EZdtdfWX0p6MGP9jljvxSY6msy/pRUD4jgNwVpv3v1QmNLlsB3LDSSUg79BRVSn7jI1QPRtArGABovAQ== - -des.js@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" - integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== - dependencies: - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -destroy@^1.0.4, destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= - -detect-indent@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" - integrity sha1-920GQ1LN9Docts5hnE7jqUdd4gg= - dependencies: - repeating "^2.0.0" - -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= - -detect-node@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" - integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== - -dezalgo@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" - integrity sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY= - dependencies: - asap "^2.0.0" - wrappy "1" - -di@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" - integrity sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw= - -diagnostics@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/diagnostics/-/diagnostics-1.1.1.tgz#cab6ac33df70c9d9a727490ae43ac995a769b22a" - integrity sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ== - dependencies: - colorspace "1.1.x" - enabled "1.0.x" - kuler "1.0.x" - -dicer@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872" - integrity sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA== - dependencies: - streamsearch "0.1.2" - -didyoumean@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.1.tgz#e92edfdada6537d484d73c0172fd1eba0c4976ff" - integrity sha1-6S7f2tplN9SE1zwBcv0eugxJdv8= - -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -diffie-hellman@^5.0.0: - version "5.0.3" - resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" - integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== - dependencies: - bn.js "^4.1.0" - miller-rabin "^4.0.0" - randombytes "^2.0.0" - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -dns-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" - integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= - -dns-packet@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" - integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg== - dependencies: - ip "^1.1.0" - safe-buffer "^5.0.1" - -dns-txt@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" - integrity sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY= - dependencies: - buffer-indexof "^1.0.0" - -dom-serialize@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b" - integrity sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs= - dependencies: - custom-event "~1.0.0" - ent "~2.2.0" - extend "^3.0.0" - void-elements "^2.0.0" - -dom-serializer@0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" - integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== - dependencies: - domelementtype "^2.0.1" - entities "^2.0.0" - -dom-storage@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/dom-storage/-/dom-storage-2.1.0.tgz#00fb868bc9201357ea243c7bcfd3304c1e34ea39" - integrity sha512-g6RpyWXzl0RR6OTElHKBl7nwnK87GUyZMYC7JWsB/IA73vpqK2K6LT39x4VepLxlSsWBFrPVLnsSR5Jyty0+2Q== - -domain-browser@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" - integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== - -domelementtype@1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" - integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== - -domelementtype@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" - integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== - -domino@^2.1.2: - version "2.1.4" - resolved "https://registry.yarnpkg.com/domino/-/domino-2.1.4.tgz#78922e7fab7c610f35792b6c745b7962d342e9c4" - integrity sha512-l70mlQ7IjPKC8kT7GljQXJZmt5OqFL+RE91ik5y5WWQtsd9wP8R7gpFnNu96fK5MqAAZRXfLLsnzKtkty5fWGQ== - -domutils@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" - integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== - dependencies: - dom-serializer "0" - domelementtype "1" - -dot-prop@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-3.0.0.tgz#1b708af094a49c9a0e7dbcad790aba539dac1177" - integrity sha1-G3CK8JSknJoOfbyteQq6U52sEXc= - dependencies: - is-obj "^1.0.0" - -dot-prop@^4.1.0, dot-prop@^4.1.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" - integrity sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ== - dependencies: - is-obj "^1.0.0" - -dot-prop@^5.1.0, dot-prop@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" - integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A== - dependencies: - is-obj "^2.0.0" - -dotenv@^6.1.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064" - integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w== - -duplexer2@~0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" - integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= - dependencies: - readable-stream "^2.0.2" - -duplexer3@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" - integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= - -duplexer@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" - integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= - -duplexify@^3.4.2, duplexify@^3.5.0, duplexify@^3.6.0: - version "3.7.1" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" - integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== - dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" - -duplexify@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.1.tgz#7027dc374f157b122a8ae08c2d3ea4d2d953aa61" - integrity sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA== - dependencies: - end-of-stream "^1.4.1" - inherits "^2.0.3" - readable-stream "^3.1.1" - stream-shift "^1.0.0" - -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - -ecdsa-sig-formatter@1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" - integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== - dependencies: - safe-buffer "^5.0.1" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= - -electron-to-chromium@^1.3.341: - version "1.3.344" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.344.tgz#f1397a633c35e726730c24be1084cd25c3ee8148" - integrity sha512-tvbx2Wl8WBR+ym3u492D0L6/jH+8NoQXqe46+QhbWH3voVPauGuZYeb1QAXYoOAWuiP2dbSvlBx0kQ1F3hu/Mw== - -electron-to-chromium@^1.3.390: - version "1.3.397" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.397.tgz#db640c2e67b08d590a504c20b56904537aa2bafa" - integrity sha512-zcUd1p/7yzTSdWkCTrqGvbnEOASy96d0RJL/lc5BDJoO23Z3G/VHd0yIPbguDU9n8QNUTCigLO7oEdtOb7fp2A== - -elliptic@^6.0.0: - version "6.5.2" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" - integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw== - dependencies: - bn.js "^4.4.0" - brorand "^1.0.1" - hash.js "^1.0.0" - hmac-drbg "^1.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.0" - -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emojis-list@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" - integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= - -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - -enabled@1.0.x: - version "1.0.2" - resolved "https://registry.yarnpkg.com/enabled/-/enabled-1.0.2.tgz#965f6513d2c2d1c5f4652b64a2e3396467fc2f93" - integrity sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M= - dependencies: - env-variable "0.0.x" - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= - -encoding@^0.1.11: - version "0.1.12" - resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" - integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= - dependencies: - iconv-lite "~0.4.13" - -end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -engine.io-client@~3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.2.1.tgz#6f54c0475de487158a1a7c77d10178708b6add36" - integrity sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw== - dependencies: - component-emitter "1.2.1" - component-inherit "0.0.3" - debug "~3.1.0" - engine.io-parser "~2.1.1" - has-cors "1.1.0" - indexof "0.0.1" - parseqs "0.0.5" - parseuri "0.0.5" - ws "~3.3.1" - xmlhttprequest-ssl "~1.5.4" - yeast "0.1.2" - -engine.io-parser@~2.1.0, engine.io-parser@~2.1.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.1.3.tgz#757ab970fbf2dfb32c7b74b033216d5739ef79a6" - integrity sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA== - dependencies: - after "0.8.2" - arraybuffer.slice "~0.0.7" - base64-arraybuffer "0.1.5" - blob "0.0.5" - has-binary2 "~1.0.2" - -engine.io@~3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.2.1.tgz#b60281c35484a70ee0351ea0ebff83ec8c9522a2" - integrity sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w== - dependencies: - accepts "~1.3.4" - base64id "1.0.0" - cookie "0.3.1" - debug "~3.1.0" - engine.io-parser "~2.1.0" - ws "~3.3.1" - -enhanced-resolve@4.1.1, enhanced-resolve@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz#2937e2b8066cd0fe7ce0990a98f0d71a35189f66" - integrity sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA== - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.5.0" - tapable "^1.0.0" - -ent@^2.2.0, ent@~2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" - integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0= - -entities@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" - integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== - -env-variable@0.0.x: - version "0.0.6" - resolved "https://registry.yarnpkg.com/env-variable/-/env-variable-0.0.6.tgz#74ab20b3786c545b62b4a4813ab8cf22726c9808" - integrity sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg== - -err-code@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960" - integrity sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA= - -errno@^0.1.1, errno@^0.1.3, errno@~0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" - integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== - dependencies: - prr "~1.0.1" - -error-ex@^1.2.0, error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es-abstract@^1.16.3, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.4: - version "1.17.4" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184" - integrity sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.1.5" - is-regex "^1.0.5" - object-inspect "^1.7.0" - object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimleft "^2.1.1" - string.prototype.trimright "^2.1.1" - -es-get-iterator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.0.tgz#bb98ad9d6d63b31aacdc8f89d5d0ee57bcb5b4c8" - integrity sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ== - dependencies: - es-abstract "^1.17.4" - has-symbols "^1.0.1" - is-arguments "^1.0.4" - is-map "^2.0.1" - is-set "^2.0.1" - is-string "^1.0.5" - isarray "^2.0.5" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -es5-ext@^0.10.12, es5-ext@^0.10.35, es5-ext@^0.10.45, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: - version "0.10.53" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" - integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== - dependencies: - es6-iterator "~2.0.3" - es6-symbol "~3.1.3" - next-tick "~1.0.0" - -es6-iterator@^2.0.3, es6-iterator@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" - integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= - dependencies: - d "1" - es5-ext "^0.10.35" - es6-symbol "^3.1.1" - -es6-promise@^4.0.3: - version "4.2.8" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" - integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== - -es6-promisify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= - dependencies: - es6-promise "^4.0.3" - -es6-symbol@^3.1.1, es6-symbol@~3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" - integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== - dependencies: - d "^1.0.1" - ext "^1.1.2" - -es6-template-strings@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/es6-template-strings/-/es6-template-strings-2.0.1.tgz#b166c6a62562f478bb7775f6ca96103a599b4b2c" - integrity sha1-sWbGpiVi9Hi7d3X2ypYQOlmbSyw= - dependencies: - es5-ext "^0.10.12" - esniff "^1.1" - -es6-weak-map@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53" - integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== - dependencies: - d "1" - es5-ext "^0.10.46" - es6-iterator "^2.0.3" - es6-symbol "^3.1.1" - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= - -escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -eslint-scope@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" - integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - -esniff@^1.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/esniff/-/esniff-1.1.0.tgz#c66849229f91464dede2e0d40201ed6abf65f2ac" - integrity sha1-xmhJIp+RRk3t4uDUAgHtar9l8qw= - dependencies: - d "1" - es5-ext "^0.10.12" - -esprima@^4.0.0, esprima@~4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esrecurse@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" - integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== - dependencies: - estraverse "^4.1.0" - -estraverse@^4.1.0, estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estree-walker@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" - integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= - -event-emitter@^0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" - integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk= - dependencies: - d "1" - es5-ext "~0.10.14" - -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - -eventemitter3@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb" - integrity sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg== - -events@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" - integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg== - -eventsource@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0" - integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ== - dependencies: - original "^1.0.0" - -evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" - integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== - dependencies: - md5.js "^1.3.4" - safe-buffer "^5.1.1" - -execa@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" - integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c= - dependencies: - cross-spawn "^5.0.1" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" - integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -exit-code@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/exit-code/-/exit-code-1.0.2.tgz#ce165811c9f117af6a5f882940b96ae7f9aecc34" - integrity sha1-zhZYEcnxF69qX4gpQLlq5/muzDQ= - -exit@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= - -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -express@^4.16.4, express@^4.17.1: - version "4.17.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" - integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== - dependencies: - accepts "~1.3.7" - array-flatten "1.1.1" - body-parser "1.19.0" - content-disposition "0.5.3" - content-type "~1.0.4" - cookie "0.4.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.2" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "~1.1.2" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.5" - qs "6.7.0" - range-parser "~1.2.1" - safe-buffer "5.1.2" - send "0.17.1" - serve-static "1.14.1" - setprototypeof "1.1.1" - statuses "~1.5.0" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -ext@^1.1.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" - integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== - dependencies: - type "^2.0.0" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -extend@3, extend@^3.0.0, extend@^3.0.2, extend@~3.0.0, extend@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -external-editor@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" - integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== - dependencies: - chardet "^0.7.0" - iconv-lite "^0.4.24" - tmp "^0.0.33" - -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= - -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= - -fast-deep-equal@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" - integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= - -fast-deep-equal@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" - integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== - -fast-glob@^3.1.1, fast-glob@^3.2.2: - version "3.2.4" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" - integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.0" - merge2 "^1.3.0" - micromatch "^4.0.2" - picomatch "^2.2.1" - -fast-json-stable-stringify@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" - integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= - -fast-json-stable-stringify@2.1.0, fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-safe-stringify@^2.0.4: - version "2.0.7" - resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" - integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== - -fast-text-encoding@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz#3e5ce8293409cfaa7177a71b9ca84e1b1e6f25ef" - integrity sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ== - -fast-url-parser@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d" - integrity sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0= - dependencies: - punycode "^1.3.2" - -fastparse@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" - integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== - -fastq@^1.6.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" - integrity sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q== - dependencies: - reusify "^1.0.4" - -faye-websocket@0.11.3, faye-websocket@~0.11.1: - version "0.11.3" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e" - integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA== - dependencies: - websocket-driver ">=0.5.1" - -faye-websocket@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" - integrity sha1-TkkvjQTftviQA1B/btvy1QHnxvQ= - dependencies: - websocket-driver ">=0.5.1" - -fecha@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fecha/-/fecha-2.3.3.tgz#948e74157df1a32fd1b12c3a3c3cdcb6ec9d96cd" - integrity sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg== - -figgy-pudding@^3.4.1, figgy-pudding@^3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" - integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w== - -figures@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" - integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= - dependencies: - escape-string-regexp "^1.0.5" - -figures@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-3.1.0.tgz#4b198dd07d8d71530642864af2d45dd9e459c4ec" - integrity sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg== - dependencies: - escape-string-regexp "^1.0.5" - -file-loader@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.0.0.tgz#97bbfaab7a2460c07bcbd72d3a6922407f67649f" - integrity sha512-/aMOAYEFXDdjG0wytpTL5YQLfZnnTmLNjn+AIrJ/6HVnTfDqLsVKUUwkDf4I4kgex36BvjuXEn/TX9B/1ESyqQ== - dependencies: - loader-utils "^2.0.0" - schema-utils "^2.6.5" - -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - -fileset@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/fileset/-/fileset-2.0.3.tgz#8e7548a96d3cc2327ee5e674168723a333bba2a0" - integrity sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA= - dependencies: - glob "^7.0.3" - minimatch "^3.0.3" - -filesize@^3.1.3: - version "3.6.1" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317" - integrity sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg== - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -finalhandler@1.1.2, finalhandler@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" - integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.3" - statuses "~1.5.0" - unpipe "~1.0.0" - -find-cache-dir@3.3.1, find-cache-dir@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" - integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ== - dependencies: - commondir "^1.0.1" - make-dir "^3.0.2" - pkg-dir "^4.1.0" - -find-cache-dir@^2.0.0, find-cache-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" - integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== - dependencies: - commondir "^1.0.1" - make-dir "^2.0.0" - pkg-dir "^3.0.0" - -find-parent-dir@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/find-parent-dir/-/find-parent-dir-0.3.0.tgz#33c44b429ab2b2f0646299c5f9f718f376ff8d54" - integrity sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ= - -find-up@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" - integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= - dependencies: - path-exists "^2.0.0" - pinkie-promise "^2.0.0" - -find-up@^2.0.0, find-up@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" - integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= - dependencies: - locate-path "^2.0.0" - -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== - dependencies: - locate-path "^3.0.0" - -find-up@^4.0.0, find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-versions@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-3.2.0.tgz#10297f98030a786829681690545ef659ed1d254e" - integrity sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww== - dependencies: - semver-regex "^2.0.0" - -firebase-admin@^8.10.0: - version "8.10.0" - resolved "https://registry.yarnpkg.com/firebase-admin/-/firebase-admin-8.10.0.tgz#4a838aec52df49845eba07ad59a40b4df996e815" - integrity sha512-QzJZ1sBh9xzKjb44aP6m1duy0Xe1ixexwh0eaOt1CkJYCOq2b6bievK4GNWMl5yGQ7FFBEbZO6hyDi+5wrctcg== - dependencies: - "@firebase/database" "^0.5.17" - "@types/node" "^8.10.59" - dicer "^0.3.0" - jsonwebtoken "8.1.0" - node-forge "0.7.4" - optionalDependencies: - "@google-cloud/firestore" "^3.0.0" - "@google-cloud/storage" "^4.1.2" - -firebase-functions-test@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/firebase-functions-test/-/firebase-functions-test-0.2.0.tgz#797125d56c23ac6b175a9308cc7887c669d4bdb0" - integrity sha512-ItONnirBztDIMVgrUeb3sTFfNHnlRhaMopQfTjWt2FZ8COvX946FzgvKMQT708LGwiUVXW/4OjfAOKaW8A2nMw== - dependencies: - "@types/lodash" "^4.14.104" - lodash "^4.17.5" - -firebase-functions@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/firebase-functions/-/firebase-functions-3.6.0.tgz#10fb2a46d13625a88299bbf9176750315f9da628" - integrity sha512-8S70Pq5nOblDKmBExq2CAgBMq+L8IDOcv10HfpDWGtgK5IpYlP0BKcchXYXcyjHtIG7xWHtR9oBLVtXFUiTp3A== - dependencies: - "@types/express" "^4.17.3" - cors "^2.8.5" - express "^4.17.1" - jsonwebtoken "^8.5.1" - lodash "^4.17.14" - -firebase-tools@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/firebase-tools/-/firebase-tools-8.0.0.tgz#3f59bbe3bfabb6c1854cfa597ad10f7afd1e0f5e" - integrity sha512-fIWWz9830evyVrgGnfqNJDkyieQMxDW7vJ2YU6jj59arhawnoALG/3sLR2jFawkKL5awsrh4NibMR1gnkrungw== - dependencies: - "@google-cloud/pubsub" "^1.1.5" - JSONStream "^1.2.1" - archiver "^3.0.0" - body-parser "^1.19.0" - chokidar "^3.0.2" - cjson "^0.3.1" - cli-color "^1.2.0" - cli-table "^0.3.1" - commander "^4.0.1" - configstore "^5.0.1" - cross-env "^5.1.3" - cross-spawn "^4.0.0" - csv-streamify "^3.0.4" - didyoumean "^1.2.1" - dotenv "^6.1.0" - exit-code "^1.0.2" - express "^4.16.4" - filesize "^3.1.3" - fs-extra "^0.23.1" - glob "^7.1.2" - google-auth-library "^5.5.0" - google-gax "~1.12.0" - inquirer "~6.3.1" - js-yaml "^3.13.1" - jsonschema "^1.0.2" - jsonwebtoken "^8.2.1" - lodash "^4.17.14" - marked "^0.7.0" - marked-terminal "^3.3.0" - minimatch "^3.0.4" - open "^6.3.0" - ora "^3.4.0" - plist "^3.0.1" - portfinder "^1.0.23" - progress "^2.0.3" - request "^2.87.0" - rimraf "^3.0.0" - semver "^5.7.1" - superstatic "^6.0.1" - tar "^4.3.0" - tcp-port-used "^1.0.1" - tmp "0.0.33" - triple-beam "^1.3.0" - universal-analytics "^0.4.16" - unzipper "^0.10.10" - update-notifier "^2.5.0" - uuid "^3.0.0" - winston "^3.0.0" - ws "^7.2.3" - -firebase@^7.13.1: - version "7.13.1" - resolved "https://registry.yarnpkg.com/firebase/-/firebase-7.13.1.tgz#f9c05ef60341cf8f7796ad7b8f3181b31be6dcf5" - integrity sha512-v4Z7Wioy/7LMC8RJn1jNlOaUqLK7bUEva+Uf+qrbtFd7hM2rWLW+0IqHZBwPrQ5tI604uHj7IlhqAZEjm38AMw== - dependencies: - "@firebase/analytics" "0.3.0" - "@firebase/app" "0.6.0" - "@firebase/app-types" "0.6.0" - "@firebase/auth" "0.14.1" - "@firebase/database" "0.5.24" - "@firebase/firestore" "1.13.0" - "@firebase/functions" "0.4.38" - "@firebase/installations" "0.4.6" - "@firebase/messaging" "0.6.10" - "@firebase/performance" "0.2.36" - "@firebase/polyfill" "0.3.33" - "@firebase/remote-config" "0.1.17" - "@firebase/storage" "0.3.30" - "@firebase/util" "0.2.43" - -flat-arguments@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/flat-arguments/-/flat-arguments-1.0.2.tgz#9baa780adf0501f282d726c9c6a038dba44ea76f" - integrity sha1-m6p4Ct8FAfKC1ybJxqA426ROp28= - dependencies: - array-flatten "^1.0.0" - as-array "^1.0.0" - lodash.isarguments "^3.0.0" - lodash.isobject "^3.0.0" - -flatted@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08" - integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg== - -flush-write-stream@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" - integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== - dependencies: - inherits "^2.0.3" - readable-stream "^2.3.6" - -follow-redirects@^1.0.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.10.0.tgz#01f5263aee921c6a54fb91667f08f4155ce169eb" - integrity sha512-4eyLK6s6lH32nOvLLwlIOnr9zrL8Sm+OvW4pVTJNoXeGzYIkHVf+pADQi+OJ0E67hiuSLezPVPyBcIZO50TmmQ== - dependencies: - debug "^3.0.0" - -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= - -forever-agent@~0.6.0, forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= - -form-data@~0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-0.2.0.tgz#26f8bc26da6440e299cbdcfb69035c4f77a6e466" - integrity sha1-Jvi8JtpkQOKZy9z7aQNcT3em5GY= - dependencies: - async "~0.9.0" - combined-stream "~0.0.4" - mime-types "~2.0.3" - -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - -forwarded@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" - integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= - -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= - -from2@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" - integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.0" - -fs-access@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/fs-access/-/fs-access-1.0.1.tgz#d6a87f262271cefebec30c553407fb995da8777a" - integrity sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o= - dependencies: - null-check "^1.0.0" - -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== - -fs-extra@4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.2.tgz#f91704c53d1b461f893452b0c307d9997647ab6b" - integrity sha1-+RcExT0bRh+JNFKwwwfZmXZHq2s= - dependencies: - graceful-fs "^4.1.2" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs-extra@^0.23.1: - version "0.23.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.23.1.tgz#6611dba6adf2ab8dc9c69fab37cddf8818157e3d" - integrity sha1-ZhHbpq3yq43Jxp+rN83fiBgVfj0= - dependencies: - graceful-fs "^4.1.2" - jsonfile "^2.1.0" - path-is-absolute "^1.0.0" - rimraf "^2.2.8" - -fs-extra@^0.30.0: - version "0.30.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0" - integrity sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A= - dependencies: - graceful-fs "^4.1.2" - jsonfile "^2.1.0" - klaw "^1.0.0" - path-is-absolute "^1.0.0" - rimraf "^2.2.8" - -fs-extra@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" - integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== - dependencies: - graceful-fs "^4.1.2" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs-extra@^8.0.1, fs-extra@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs-extra@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" - integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^1.0.0" - -fs-minipass@^1.2.5: - version "1.2.7" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" - integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== - dependencies: - minipass "^2.6.0" - -fs-minipass@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" - integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== - dependencies: - minipass "^3.0.0" - -fs-write-stream-atomic@^1.0.8: - version "1.0.10" - resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" - integrity sha1-tH31NJPvkR33VzHnCp3tAYnbQMk= - dependencies: - graceful-fs "^4.1.2" - iferr "^0.1.5" - imurmurhash "^0.1.4" - readable-stream "1 || 2" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fsevents@^1.2.7: - version "1.2.11" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.11.tgz#67bf57f4758f02ede88fb2a1712fef4d15358be3" - integrity sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw== - dependencies: - bindings "^1.5.0" - nan "^2.12.1" - -fsevents@~2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" - integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== - -fstream@^1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" - integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== - dependencies: - graceful-fs "^4.1.2" - inherits "~2.0.0" - mkdirp ">=0.5 0" - rimraf "2" - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= - -fuzzy@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/fuzzy/-/fuzzy-0.1.3.tgz#4c76ec2ff0ac1a36a9dccf9a00df8623078d4ed8" - integrity sha1-THbsL/CsGjap3M+aAN+GIweNTtg= - -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - -gaxios@^2.0.0, gaxios@^2.0.1, gaxios@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-2.3.0.tgz#442eb57c6f00811795946a89a13dfe47bdb9ba40" - integrity sha512-VgC4JKJQAAAGK5rFZbPcS5mXsdIYVMIUJOxMjSOkYdfhB74R0L6y8PFQDdS0r1ObG6hdP11e71EjHh3xbI+6fQ== - dependencies: - abort-controller "^3.0.0" - extend "^3.0.2" - https-proxy-agent "^4.0.0" - is-stream "^2.0.0" - node-fetch "^2.3.0" - -gcp-metadata@^3.3.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-3.3.1.tgz#1c5c05591e7a9c1613463b05dab9642319f08f1f" - integrity sha512-RrASg1HaVAxoB9Q/8sYfJ++v9PMiiqIgOrOxZeagMgS4osZtICT1lKBx2uvzYgwetxj8i6K99Z0iuKMg7WraTg== - dependencies: - gaxios "^2.1.0" - json-bigint "^0.3.0" - -gcs-resumable-upload@^2.2.4: - version "2.3.2" - resolved "https://registry.yarnpkg.com/gcs-resumable-upload/-/gcs-resumable-upload-2.3.2.tgz#f04a7459483f871f0de71db7454296938688a296" - integrity sha512-OPS0iAmPCV+r7PziOIhyxmQOzsazFCy76yYDOS/Z80O/7cuny1KMfqDQa2T0jLaL8EreTU7EMZG5pUuqBKgzHA== - dependencies: - abort-controller "^3.0.0" - configstore "^5.0.0" - gaxios "^2.0.0" - google-auth-library "^5.0.0" - pumpify "^2.0.0" - stream-events "^1.0.4" - -generate-function@^2.0.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" - integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== - dependencies: - is-property "^1.0.2" - -generate-object-property@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" - integrity sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA= - dependencies: - is-property "^1.0.0" - -genfun@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/genfun/-/genfun-5.0.0.tgz#9dd9710a06900a5c4a5bf57aca5da4e52fe76537" - integrity sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA== - -gensync@^1.0.0-beta.1: - version "1.0.0-beta.1" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" - integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== - -get-caller-file@^2.0.1: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-pkg-repo@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz#c73b489c06d80cc5536c2c853f9e05232056972d" - integrity sha1-xztInAbYDMVTbCyFP54FIyBWly0= - dependencies: - hosted-git-info "^2.1.4" - meow "^3.3.0" - normalize-package-data "^2.3.0" - parse-github-repo-url "^1.3.0" - through2 "^2.0.0" - -get-stdin@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" - integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= - -get-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= - -get-stream@^4.0.0, get-stream@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" - -get-stream@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" - integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== - dependencies: - pump "^3.0.0" - -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= - -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= - dependencies: - assert-plus "^1.0.0" - -git-raw-commits@^1.3.6: - version "1.3.6" - resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-1.3.6.tgz#27c35a32a67777c1ecd412a239a6c19d71b95aff" - integrity sha512-svsK26tQ8vEKnMshTDatSIQSMDdz8CxIIqKsvPqbtV23Etmw6VNaFAitu8zwZ0VrOne7FztwPyRLxK7/DIUTQg== - dependencies: - dargs "^4.0.1" - lodash.template "^4.0.2" - meow "^4.0.0" - split2 "^2.0.0" - through2 "^2.0.0" - -git-remote-origin-url@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz#5282659dae2107145a11126112ad3216ec5fa65f" - integrity sha1-UoJlna4hBxRaERJhEq0yFuxfpl8= - dependencies: - gitconfiglocal "^1.0.0" - pify "^2.3.0" - -git-semver-tags@^1.3.6: - version "1.3.6" - resolved "https://registry.yarnpkg.com/git-semver-tags/-/git-semver-tags-1.3.6.tgz#357ea01f7280794fe0927f2806bee6414d2caba5" - integrity sha512-2jHlJnln4D/ECk9FxGEBh3k44wgYdWjWDtMmJPaecjoRmxKo3Y1Lh8GMYuOPu04CHw86NTAODchYjC5pnpMQig== - dependencies: - meow "^4.0.0" - semver "^5.5.0" - -gitconfiglocal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz#41d045f3851a5ea88f03f24ca1c6178114464b9b" - integrity sha1-QdBF84UaXqiPA/JMocYXgRRGS5s= - dependencies: - ini "^1.3.2" - -glob-parent@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= - dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" - -glob-parent@^5.1.0, glob-parent@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== - dependencies: - is-glob "^4.0.1" - -glob-parent@~5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" - integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== - dependencies: - is-glob "^4.0.1" - -glob-slash@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/glob-slash/-/glob-slash-1.0.0.tgz#fe52efa433233f74a2fe64c7abb9bc848202ab95" - integrity sha1-/lLvpDMjP3Si/mTHq7m8hIICq5U= - -glob-slasher@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/glob-slasher/-/glob-slasher-1.0.1.tgz#747a0e5bb222642ee10d3e05443e109493cb0f8e" - integrity sha1-dHoOW7IiZC7hDT4FRD4QlJPLD44= - dependencies: - glob-slash "^1.0.0" - lodash.isobject "^2.4.1" - toxic "^1.0.0" - -glob@4.3: - version "4.3.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-4.3.5.tgz#80fbb08ca540f238acce5d11d1e9bc41e75173d3" - integrity sha1-gPuwjKVA8jiszl0R0em8QedRc9M= - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "^2.0.1" - once "^1.3.0" - -glob@5.0.x: - version "5.0.15" - resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" - integrity sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E= - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@7.1.6, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^3.2.11, glob@~3.2: - version "3.2.11" - resolved "https://registry.yarnpkg.com/glob/-/glob-3.2.11.tgz#4a973f635b9190f715d10987d5c00fd2815ebe3d" - integrity sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0= - dependencies: - inherits "2" - minimatch "0.3" - -global-dirs@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" - integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU= - dependencies: - ini "^1.3.4" - -global-dirs@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201" - integrity sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A== - dependencies: - ini "^1.3.5" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -globals@^9.18.0: - version "9.18.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" - integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== - -globby@^11.0.1: - version "11.0.1" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" - integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.1.1" - ignore "^5.1.4" - merge2 "^1.3.0" - slash "^3.0.0" - -globby@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" - integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw= - dependencies: - array-union "^1.0.1" - glob "^7.0.3" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - -google-auth-library@^5.0.0, google-auth-library@^5.5.0: - version "5.9.2" - resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-5.9.2.tgz#e528f4f1cd10657073d7ae2b9a9ce17ac97c3538" - integrity sha512-rBE1YTOZ3/Hu6Mojkr+UUmbdc/F28hyMGYEGxjyfVA9ZFmq12oqS3AeftX4h9XpdVIcxPooSo8hECYGT6B9XqQ== - dependencies: - arrify "^2.0.0" - base64-js "^1.3.0" - fast-text-encoding "^1.0.0" - gaxios "^2.1.0" - gcp-metadata "^3.3.0" - gtoken "^4.1.0" - jws "^4.0.0" - lru-cache "^5.0.0" - -google-gax@^1.13.0, google-gax@^1.7.5: - version "1.14.1" - resolved "https://registry.yarnpkg.com/google-gax/-/google-gax-1.14.1.tgz#8271a5293b7b61377d548289964b7e8209984381" - integrity sha512-lAvILUMnXL+BVSSlbzwpGzs3ZP2r+b1l44zeDTRWceejDgyZORKdPEEhtUw49x9CVwxpPx02+v0yktqnRhUD1A== - dependencies: - "@grpc/grpc-js" "^0.6.12" - "@grpc/proto-loader" "^0.5.1" - "@types/fs-extra" "^8.0.1" - "@types/long" "^4.0.0" - abort-controller "^3.0.0" - duplexify "^3.6.0" - google-auth-library "^5.0.0" - is-stream-ended "^0.1.4" - lodash.at "^4.6.0" - lodash.has "^4.5.2" - node-fetch "^2.6.0" - protobufjs "^6.8.8" - retry-request "^4.0.0" - semver "^6.0.0" - walkdir "^0.4.0" - -google-gax@~1.12.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/google-gax/-/google-gax-1.12.0.tgz#f926f7e6abda245db38ecbebbbf58daaf3a8f687" - integrity sha512-BeeoxVO6y9K20gUsexUwptutd0PfrTItrA02JWwwstlBIOAcvgFp86MHWufQsnrkPVhxBjHXq65aIkSejtJjDg== - dependencies: - "@grpc/grpc-js" "^0.6.12" - "@grpc/proto-loader" "^0.5.1" - "@types/long" "^4.0.0" - abort-controller "^3.0.0" - duplexify "^3.6.0" - google-auth-library "^5.0.0" - is-stream-ended "^0.1.4" - lodash.at "^4.6.0" - lodash.has "^4.5.2" - node-fetch "^2.6.0" - protobufjs "^6.8.8" - retry-request "^4.0.0" - semver "^6.0.0" - walkdir "^0.4.0" - -google-p12-pem@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-2.0.4.tgz#036462394e266472632a78b685f0cc3df4ef337b" - integrity sha512-S4blHBQWZRnEW44OcR7TL9WR+QCqByRvhNDZ/uuQfpxywfupikf/miba8js1jZi6ZOGv5slgSuoshCWh6EMDzg== - dependencies: - node-forge "^0.9.0" - -got@^6.7.1: - version "6.7.1" - resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" - integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA= - dependencies: - create-error-class "^3.0.0" - duplexer3 "^0.1.4" - get-stream "^3.0.0" - is-redirect "^1.0.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" - lowercase-keys "^1.0.0" - safe-buffer "^5.0.1" - timed-out "^4.0.0" - unzip-response "^2.0.1" - url-parse-lax "^1.0.0" - -got@^9.6.0: - version "9.6.0" - resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" - integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== - dependencies: - "@sindresorhus/is" "^0.14.0" - "@szmarczak/http-timer" "^1.1.2" - cacheable-request "^6.0.0" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^4.1.0" - lowercase-keys "^1.0.1" - mimic-response "^1.0.1" - p-cancelable "^1.0.0" - to-readable-stream "^1.0.0" - url-parse-lax "^3.0.0" - -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.2: - version "4.2.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" - integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== - -"graceful-readlink@>= 1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" - integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU= - -grpc@1.24.2: - version "1.24.2" - resolved "https://registry.yarnpkg.com/grpc/-/grpc-1.24.2.tgz#76d047bfa7b05b607cbbe3abb99065dcefe0c099" - integrity sha512-EG3WH6AWMVvAiV15d+lr+K77HJ/KV/3FvMpjKjulXHbTwgDZkhkcWbwhxFAoTdxTkQvy0WFcO3Nog50QBbHZWw== - dependencies: - "@types/bytebuffer" "^5.0.40" - lodash.camelcase "^4.3.0" - lodash.clone "^4.5.0" - nan "^2.13.2" - node-pre-gyp "^0.14.0" - protobufjs "^5.0.3" - -gtoken@^4.1.0: - version "4.1.4" - resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-4.1.4.tgz#925ff1e7df3aaada06611d30ea2d2abf60fcd6a7" - integrity sha512-VxirzD0SWoFUo5p8RDP8Jt2AGyOmyYcT/pOUgDKJCK+iSw0TMqwrVfY37RXTNmoKwrzmDHSk0GMT9FsgVmnVSA== - dependencies: - gaxios "^2.1.0" - google-p12-pem "^2.0.0" - jws "^4.0.0" - mime "^2.2.0" - -gzip-size@*, gzip-size@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" - integrity sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA== - dependencies: - duplexer "^0.1.1" - pify "^4.0.1" - -handle-thing@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754" - integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ== - -handlebars@^4.0.2, handlebars@^4.7.2: - version "4.7.2" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.2.tgz#01127b3840156a0927058779482031afe0e730d7" - integrity sha512-4PwqDL2laXtTWZghzzCtunQUTLbo31pcCJrd/B/9JP8XbhVzpS5ZXuKqlOzsd1rtcaLo4KqAn8nl8mkknS4MHw== - dependencies: - neo-async "^2.6.0" - optimist "^0.6.1" - source-map "^0.6.1" - optionalDependencies: - uglify-js "^3.1.4" - -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= - -har-validator@^1.6.1: - version "1.8.0" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-1.8.0.tgz#d83842b0eb4c435960aeb108a067a3aa94c0eeb2" - integrity sha1-2DhCsOtMQ1lgrrEIoGejqpTA7rI= - dependencies: - bluebird "^2.9.30" - chalk "^1.0.0" - commander "^2.8.1" - is-my-json-valid "^2.12.0" - -har-validator@~5.1.0: - version "5.1.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" - integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== - dependencies: - ajv "^6.5.5" - har-schema "^2.0.0" - -has-ansi@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-0.1.0.tgz#84f265aae8c0e6a88a12d7022894b7568894c62e" - integrity sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4= - dependencies: - ansi-regex "^0.2.0" - -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= - dependencies: - ansi-regex "^2.0.0" - -has-binary2@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d" - integrity sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw== - dependencies: - isarray "2.0.1" - -has-cors@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" - integrity sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk= - -has-flag@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" - integrity sha1-6CB68cx7MNRGzHC3NLXovhj4jVE= - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-symbols@^1.0.0, has-symbols@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" - integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== - -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= - -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -has-yarn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" - integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== - -has@^1.0.0, has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -hash-base@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" - integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg= - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -hash-stream-validation@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/hash-stream-validation/-/hash-stream-validation-0.2.2.tgz#6b34c4fce5e9fce265f1d3380900049d92a10090" - integrity sha512-cMlva5CxWZOrlS/cY0C+9qAzesn5srhFA8IT1VPiHc9bWWBLkJfEUIZr7MWoi89oOOGmpg8ymchaOjiArsGu5A== - dependencies: - through2 "^2.0.0" - -hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" - integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.1" - -hawk@~2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/hawk/-/hawk-2.3.1.tgz#1e731ce39447fa1d0f6d707f7bceebec0fd1ec1f" - integrity sha1-HnMc45RH+h0PbXB/e87r7A/R7B8= - dependencies: - boom "2.x.x" - cryptiles "2.x.x" - hoek "2.x.x" - sntp "1.x.x" - -hex-color-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" - integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== - -highlight.js@^9.17.1: - version "9.18.0" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.0.tgz#6b1763cfcd53744313bd3f31f1210f7beb962c79" - integrity sha512-A97kI1KAUzKoAiEoaGcf2O9YPS8nbDTCRFokaaeBhnqjQTvbAuAJrQMm21zw8s8xzaMtCQBtgbyGXLGxdxQyqQ== - -hmac-drbg@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= - dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" - -hoek@2.x.x: - version "2.16.3" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" - integrity sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0= - -home-dir@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/home-dir/-/home-dir-1.0.0.tgz#2917eb44bdc9072ceda942579543847e3017fe4e" - integrity sha1-KRfrRL3JByztqUJXlUOEfjAX/k4= - -home-or-tmp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" - integrity sha1-42w/LSyufXRqhX440Y1fMqeILbg= - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.1" - -hosted-git-info@^2.1.4, hosted-git-info@^2.7.1: - version "2.8.5" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.5.tgz#759cfcf2c4d156ade59b0b2dfabddc42a6b9c70c" - integrity sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg== - -hosted-git-info@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.2.tgz#8b7e3bd114b59b51786f8bade0f39ddc80275a97" - integrity sha512-ezZMWtHXm7Eb7Rq4Mwnx2vs79WUx2QmRg3+ZqeGroKzfDO+EprOcgRPYghsOP9JuYBfK18VojmRTGCg8Ma+ktw== - dependencies: - lru-cache "^5.1.1" - -hpack.js@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI= - dependencies: - inherits "^2.0.1" - obuf "^1.0.0" - readable-stream "^2.0.1" - wbuf "^1.1.0" - -hsl-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" - integrity sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4= - -hsla-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" - integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= - -html-comment-regex@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" - integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== - -html-entities@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.3.1.tgz#fb9a1a4b5b14c5daba82d3e34c6ae4fe701a0e44" - integrity sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA== - -html-escaper@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.0.tgz#71e87f931de3fe09e56661ab9a29aadec707b491" - integrity sha512-a4u9BeERWGu/S8JiWEAQcdrg9v4QArtP9keViQjGMdff20fBdd8waotXaNmODqBe6uZ3Nafi7K/ho4gCQHV3Ig== - -http-cache-semantics@^3.8.1: - version "3.8.1" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" - integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== - -http-cache-semantics@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz#495704773277eeef6e43f9ab2c2c7d259dda25c5" - integrity sha512-TcIMG3qeVLgDr1TEd2XvHaTnMPwYQUQMIBLy+5pLSDKYFc7UIqj39w8EGzZkaxoLv/l2K8HaI0t5AVA+YYgUew== - -http-deceiver@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= - -http-errors@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" - integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - -http-errors@~1.7.2: - version "1.7.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" - integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -"http-parser-js@>=0.4.0 <0.4.11": - version "0.4.10" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4" - integrity sha1-ksnBN0w1CF912zWexWzCV8u5P6Q= - -http-proxy-agent@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" - integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== - dependencies: - agent-base "4" - debug "3.1.0" - -http-proxy-agent@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.0.tgz#6b74d332e1934a1107b97e97de4a00e267c790fe" - integrity sha512-GX0FA6+IcDf4Oxc/FBWgYj4zKgo/DnZrksaG9jyuQLExs6xlX+uI5lcA8ymM3JaZTRrF/4s2UX19wJolyo7OBA== - dependencies: - agent-base "6" - debug "4" - -http-proxy-middleware@0.19.1: - version "0.19.1" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" - integrity sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q== - dependencies: - http-proxy "^1.17.0" - is-glob "^4.0.0" - lodash "^4.17.11" - micromatch "^3.1.10" - -http-proxy@^1.13.0, http-proxy@^1.17.0: - version "1.18.0" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.0.tgz#dbe55f63e75a347db7f3d99974f2692a314a6a3a" - integrity sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ== - dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - -http-signature@~0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-0.11.0.tgz#1796cf67a001ad5cd6849dca0991485f09089fe6" - integrity sha1-F5bPZ6ABrVzWhJ3KCZFIXwkIn+Y= - dependencies: - asn1 "0.1.11" - assert-plus "^0.1.5" - ctype "0.5.3" - -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - -https-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" - integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= - -https-proxy-agent@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz#35f7da6c48ce4ddbfa264891ac593ee5ff8671e6" - integrity sha1-NffabEjOTdv6JkiRrFk+5f+GceY= - dependencies: - agent-base "2" - debug "2" - extend "3" - -https-proxy-agent@^2.2.3: - version "2.2.4" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" - integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== - dependencies: - agent-base "^4.3.0" - debug "^3.1.0" - -https-proxy-agent@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" - integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg== - dependencies: - agent-base "5" - debug "4" - -humanize-ms@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" - integrity sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0= - dependencies: - ms "^2.0.0" - -husky@^4.2.5: - version "4.2.5" - resolved "https://registry.yarnpkg.com/husky/-/husky-4.2.5.tgz#2b4f7622673a71579f901d9885ed448394b5fa36" - integrity sha512-SYZ95AjKcX7goYVZtVZF2i6XiZcHknw50iXvY7b0MiGoj5RwdgRQNEHdb+gPDPCXKlzwrybjFjkL6FOj8uRhZQ== - dependencies: - chalk "^4.0.0" - ci-info "^2.0.0" - compare-versions "^3.6.0" - cosmiconfig "^6.0.0" - find-versions "^3.2.0" - opencollective-postinstall "^2.0.2" - pkg-dir "^4.2.0" - please-upgrade-node "^3.2.0" - slash "^3.0.0" - which-pm-runs "^1.0.0" - -iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -icss-utils@^4.0.0, icss-utils@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" - integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA== - dependencies: - postcss "^7.0.14" - -idb@3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/idb/-/idb-3.0.2.tgz#c8e9122d5ddd40f13b60ae665e4862f8b13fa384" - integrity sha512-+FLa/0sTXqyux0o6C+i2lOR0VoS60LU/jzUo5xjfY6+7sEEgy4Gz1O7yFBXvjd7N0NyIGWIRg8DcQSLEG+VSPw== - -ieee754@^1.1.4: - version "1.1.13" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" - integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== - -iferr@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" - integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= - -ignore-walk@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" - integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw== - dependencies: - minimatch "^3.0.4" - -ignore@^5.1.4: - version "5.1.8" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" - integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== - -image-size@~0.5.0: - version "0.5.5" - resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" - integrity sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w= - -import-cwd@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" - integrity sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk= - dependencies: - import-from "^2.1.0" - -import-fresh@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" - integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= - dependencies: - caller-path "^2.0.0" - resolve-from "^3.0.0" - -import-fresh@^3.1.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" - integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -import-from@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1" - integrity sha1-M1238qev/VOqpHHUuAId7ja387E= - dependencies: - resolve-from "^3.0.0" - -import-lazy@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" - integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= - -import-local@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" - integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== - dependencies: - pkg-dir "^3.0.0" - resolve-cwd "^2.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= - -indent-string@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" - integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= - dependencies: - repeating "^2.0.0" - -indent-string@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" - integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok= - -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - -indexes-of@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" - integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= - -indexof@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" - integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10= - -infer-owner@^1.0.3, infer-owner@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" - integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inherits@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" - integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -ini@1.3.5, ini@^1.3.2, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== - -injection-js@^2.2.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/injection-js/-/injection-js-2.3.0.tgz#56d807282dfaf2f9663f45373c4206d7793c5dd6" - integrity sha512-rhS6E5jv603kbaO72ylOt0hGF1LT03oqQ4GU5KOO0qSaRbIWmdUCHjXq+VT79jL6/NmXtw9ccfK6dh/CzjoYjA== - -inquirer-autocomplete-prompt@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/inquirer-autocomplete-prompt/-/inquirer-autocomplete-prompt-1.0.2.tgz#3f2548f73dd12f0a541be055ea9c8c7aedeb42bf" - integrity sha512-vNmAhhrOQwPnUm4B9kz1UB7P98rVF1z8txnjp53r40N0PBCuqoRWqjg3Tl0yz0UkDg7rEUtZ2OZpNc7jnOU9Zw== - dependencies: - ansi-escapes "^3.0.0" - chalk "^2.0.0" - figures "^2.0.0" - run-async "^2.3.0" - -inquirer@7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.1.0.tgz#1298a01859883e17c7264b82870ae1034f92dd29" - integrity sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg== - dependencies: - ansi-escapes "^4.2.1" - chalk "^3.0.0" - cli-cursor "^3.1.0" - cli-width "^2.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.15" - mute-stream "0.0.8" - run-async "^2.4.0" - rxjs "^6.5.3" - string-width "^4.1.0" - strip-ansi "^6.0.0" - through "^2.3.6" - -inquirer@^6.2.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" - integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== - dependencies: - ansi-escapes "^3.2.0" - chalk "^2.4.2" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^3.0.3" - figures "^2.0.0" - lodash "^4.17.12" - mute-stream "0.0.7" - run-async "^2.2.0" - rxjs "^6.4.0" - string-width "^2.1.0" - strip-ansi "^5.1.0" - through "^2.3.6" - -inquirer@~6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.3.1.tgz#7a413b5e7950811013a3db491c61d1f3b776e8e7" - integrity sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA== - dependencies: - ansi-escapes "^3.2.0" - chalk "^2.4.2" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^3.0.3" - figures "^2.0.0" - lodash "^4.17.11" - mute-stream "0.0.7" - run-async "^2.2.0" - rxjs "^6.4.0" - string-width "^2.1.0" - strip-ansi "^5.1.0" - through "^2.3.6" - -internal-ip@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" - integrity sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg== - dependencies: - default-gateway "^4.2.0" - ipaddr.js "^1.9.0" - -interpret@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" - integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== - -invariant@^2.2.2, invariant@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" - integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== - dependencies: - loose-envify "^1.0.0" - -invert-kv@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" - integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= - -ip-regex@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" - integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= - -ip@1.1.5, ip@^1.1.0, ip@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= - -ipaddr.js@1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65" - integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA== - -ipaddr.js@^1.9.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -is-absolute-url@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" - integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= - -is-absolute-url@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" - integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== - -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - -is-arguments@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" - integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= - -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - -is-bigint@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.0.tgz#73da8c33208d00f130e9b5e15d23eac9215601c4" - integrity sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g== - -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= - dependencies: - binary-extensions "^1.0.0" - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-boolean-object@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e" - integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ== - -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - -is-callable@^1.1.4, is-callable@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" - integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== - -is-ci@^1.0.10: - version "1.2.1" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" - integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg== - dependencies: - ci-info "^1.5.0" - -is-ci@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" - integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== - dependencies: - ci-info "^2.0.0" - -is-color-stop@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" - integrity sha1-z/9HGu5N1cnhWFmPvhKWe1za00U= - dependencies: - css-color-names "^0.0.4" - hex-color-regex "^1.1.0" - hsl-regex "^1.0.0" - hsla-regex "^1.0.0" - rgb-regex "^1.0.1" - rgba-regex "^1.0.0" - -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - -is-date-object@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" - integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== - -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-directory@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" - integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= - -is-docker@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.0.0.tgz#2cb0df0e75e2d064fe1864c37cdeacb7b2dcf25b" - integrity sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ== - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - -is-extglob@^2.1.0, is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-finite@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" - integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko= - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= - dependencies: - is-extglob "^2.1.0" - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" - -is-installed-globally@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" - integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA= - dependencies: - global-dirs "^0.1.0" - is-path-inside "^1.0.0" - -is-installed-globally@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.1.tgz#679afef819347a72584617fd19497f010b8ed35f" - integrity sha512-oiEcGoQbGc+3/iijAijrK2qFpkNoNjsHOm/5V5iaeydyrS/hnwaRCEgH5cpW0P3T1lSjV5piB7S5b5lEugNLhg== - dependencies: - global-dirs "^2.0.1" - is-path-inside "^3.0.1" - -is-interactive@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" - integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== - -is-map@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.1.tgz#520dafc4307bb8ebc33b813de5ce7c9400d644a1" - integrity sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw== - -is-module@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" - integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= - -is-my-ip-valid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824" - integrity sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ== - -is-my-json-valid@^2.12.0: - version "2.20.0" - resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.20.0.tgz#1345a6fca3e8daefc10d0fa77067f54cedafd59a" - integrity sha512-XTHBZSIIxNsIsZXg7XB5l8z/OBFosl1Wao4tXLpeC7eKU4Vm/kdop2azkPqULwnfGQjmeDIyey9g7afMMtdWAA== - dependencies: - generate-function "^2.0.0" - generate-object-property "^1.1.0" - is-my-ip-valid "^1.0.0" - jsonpointer "^4.0.0" - xtend "^4.0.0" - -is-npm@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" - integrity sha1-8vtjpl5JBbQGyGBydloaTceTufQ= - -is-npm@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" - integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== - -is-number-object@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" - integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-obj@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" - integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= - -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - -is-path-cwd@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" - integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== - -is-path-in-cwd@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz#bfe2dca26c69f397265a4009963602935a053acb" - integrity sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ== - dependencies: - is-path-inside "^2.1.0" - -is-path-inside@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" - integrity sha1-jvW33lBDej/cprToZe96pVy0gDY= - dependencies: - path-is-inside "^1.0.1" - -is-path-inside@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2" - integrity sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg== - dependencies: - path-is-inside "^1.0.2" - -is-path-inside@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" - integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg== - -is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= - -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-promise@^2.1, is-promise@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" - integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= - -is-property@^1.0.0, is-property@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" - integrity sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ= - -is-redirect@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" - integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ= - -is-reference@^1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.1.4.tgz#3f95849886ddb70256a3e6d062b1a68c13c51427" - integrity sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw== - dependencies: - "@types/estree" "0.0.39" - -is-regex@^1.0.4, is-regex@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" - integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== - dependencies: - has "^1.0.3" - -is-resolvable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" - integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== - -is-retry-allowed@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" - integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== - -is-set@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.1.tgz#d1604afdab1724986d30091575f54945da7e5f43" - integrity sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA== - -is-stream-ended@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-stream-ended/-/is-stream-ended-0.1.4.tgz#f50224e95e06bce0e356d440a4827cd35b267eda" - integrity sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw== - -is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= - -is-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" - integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== - -is-string@^1.0.4, is-string@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" - integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== - -is-subset@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" - integrity sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY= - -is-svg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" - integrity sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ== - dependencies: - html-comment-regex "^1.1.0" - -is-symbol@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" - integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== - dependencies: - has-symbols "^1.0.1" - -is-text-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e" - integrity sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4= - dependencies: - text-extensions "^1.0.0" - -is-typedarray@^1.0.0, is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -is-url@^1.2.2: - version "1.2.4" - resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" - integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== - -is-utf8@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" - integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= - -is-weakmap@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" - integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== - -is-weakset@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.1.tgz#e9a0af88dbd751589f5e50d80f4c98b780884f83" - integrity sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw== - -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -is-wsl@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" - integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= - -is-wsl@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d" - integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog== - -is-yarn-global@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" - integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== - -is2@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is2/-/is2-2.0.1.tgz#8ac355644840921ce435d94f05d3a94634d3481a" - integrity sha512-+WaJvnaA7aJySz2q/8sLjMb2Mw14KTplHmSwcSpZ/fWJPkUmqw3YTzSWbPJ7OAwRvdYTWF2Wg+yYJ1AdP5Z8CA== - dependencies: - deep-is "^0.1.3" - ip-regex "^2.1.0" - is-url "^1.2.2" - -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= - -isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -isarray@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" - integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4= - -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - -isbinaryfile@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-3.0.3.tgz#5d6def3edebf6e8ca8cae9c30183a804b5f8be80" - integrity sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw== - dependencies: - buffer-alloc "^1.2.0" - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= - -isomorphic-fetch@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" - integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= - dependencies: - node-fetch "^1.0.1" - whatwg-fetch ">=0.10.0" - -isstream@~0.1.1, isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= - -istanbul-api@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-2.1.6.tgz#d61702a9d1c66ad89d92e66d401e16b0bda4a35f" - integrity sha512-x0Eicp6KsShG1k1rMgBAi/1GgY7kFGEBwQpw3PXGEmu+rBcBNhqU8g2DgY9mlepAsLPzrzrbqSgCGANnki4POA== - dependencies: - async "^2.6.2" - compare-versions "^3.4.0" - fileset "^2.0.3" - istanbul-lib-coverage "^2.0.5" - istanbul-lib-hook "^2.0.7" - istanbul-lib-instrument "^3.3.0" - istanbul-lib-report "^2.0.8" - istanbul-lib-source-maps "^3.0.6" - istanbul-reports "^2.2.4" - js-yaml "^3.13.1" - make-dir "^2.1.0" - minimatch "^3.0.4" - once "^1.4.0" - -istanbul-lib-coverage@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49" - integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA== - -istanbul-lib-coverage@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec" - integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg== - -istanbul-lib-hook@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz#c95695f383d4f8f60df1f04252a9550e15b5b133" - integrity sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA== - dependencies: - append-transform "^1.0.0" - -istanbul-lib-instrument@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz#a5f63d91f0bbc0c3e479ef4c5de027335ec6d630" - integrity sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA== - dependencies: - "@babel/generator" "^7.4.0" - "@babel/parser" "^7.4.3" - "@babel/template" "^7.4.0" - "@babel/traverse" "^7.4.3" - "@babel/types" "^7.4.0" - istanbul-lib-coverage "^2.0.5" - semver "^6.0.0" - -istanbul-lib-instrument@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.1.tgz#61f13ac2c96cfefb076fe7131156cc05907874e6" - integrity sha512-imIchxnodll7pvQBYOqUu88EufLCU56LMeFPZZM/fJZ1irYcYdqroaV+ACK1Ila8ls09iEYArp+nqyC6lW1Vfg== - dependencies: - "@babel/core" "^7.7.5" - "@babel/parser" "^7.7.5" - "@babel/template" "^7.7.4" - "@babel/traverse" "^7.7.4" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.0.0" - semver "^6.3.0" - -istanbul-lib-report@^2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz#5a8113cd746d43c4889eba36ab10e7d50c9b4f33" - integrity sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ== - dependencies: - istanbul-lib-coverage "^2.0.5" - make-dir "^2.1.0" - supports-color "^6.1.0" - -istanbul-lib-source-maps@^3.0.6: - version "3.0.6" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz#284997c48211752ec486253da97e3879defba8c8" - integrity sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw== - dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^2.0.5" - make-dir "^2.1.0" - rimraf "^2.6.3" - source-map "^0.6.1" - -istanbul-reports@^2.2.4: - version "2.2.7" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.2.7.tgz#5d939f6237d7b48393cc0959eab40cd4fd056931" - integrity sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg== - dependencies: - html-escaper "^2.0.0" - -jasmine-core@^3.3, jasmine-core@^3.4.0, jasmine-core@~3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.5.0.tgz#132c23e645af96d85c8bca13c8758b18429fc1e4" - integrity sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA== - -jasmine-core@~2.3.0: - version "2.3.4" - resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.3.4.tgz#ad772662b32d16c564ee25c863edae1d65a038f2" - integrity sha1-rXcmYrMtFsVk7iXIY+2uHWWgOPI= - -jasmine@2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-2.3.2.tgz#febf0e4b4afff63c2d09211f077da0b859f5f191" - integrity sha1-/r8OS0r/9jwtCSEfB32guFn18ZE= - dependencies: - exit "^0.1.2" - glob "^3.2.11" - jasmine-core "~2.3.0" - -jasmine@^3.4.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.5.0.tgz#7101eabfd043a1fc82ac24e0ab6ec56081357f9e" - integrity sha512-DYypSryORqzsGoMazemIHUfMkXM7I7easFaxAvNM3Mr6Xz3Fy36TupTrAOxZWN8MVKEU5xECv22J4tUQf3uBzQ== - dependencies: - glob "^7.1.4" - jasmine-core "~3.5.0" - -jasminewd2@0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/jasminewd2/-/jasminewd2-0.0.6.tgz#259157f06d2d149fbecb96302c80aa322a377222" - integrity sha1-JZFX8G0tFJ++y5YwLICqMio3ciI= - -jest-worker@25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.1.0.tgz#75d038bad6fdf58eba0d2ec1835856c497e3907a" - integrity sha512-ZHhHtlxOWSxCoNOKHGbiLzXnl42ga9CxDr27H36Qn+15pQZd3R/F24jrmjDelw9j/iHUIWMWs08/u2QN50HHOg== - dependencies: - merge-stream "^2.0.0" - supports-color "^7.0.0" - -jest-worker@^26.0.0: - version "26.1.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.1.0.tgz#65d5641af74e08ccd561c240e7db61284f82f33d" - integrity sha512-Z9P5pZ6UC+kakMbNJn+tA2RdVdNX5WH1x+5UCBZ9MxIK24pjYtFt96fK+UwBTrjLYm232g1xz0L3eTh51OW+yQ== - dependencies: - merge-stream "^2.0.0" - supports-color "^7.0.0" - -jju@^1.1.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a" - integrity sha1-o6vicYryQaKykE+EpiWXDzia4yo= - -join-path@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/join-path/-/join-path-1.1.1.tgz#10535a126d24cbd65f7ffcdf15ef2e631076b505" - integrity sha1-EFNaEm0ky9Zff/zfFe8uYxB2tQU= - dependencies: - as-array "^2.0.0" - url-join "0.0.1" - valid-url "^1" - -jquery@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2" - integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw== - -"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-tokens@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" - integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= - -js-yaml@^3.13.1: - version "3.13.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" - integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - -jsesc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" - integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s= - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= - -json-bigint@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-0.3.0.tgz#0ccd912c4b8270d05f056fbd13814b53d3825b1e" - integrity sha1-DM2RLEuCcNBfBW+9E4FLU9OCWx4= - dependencies: - bignumber.js "^7.0.0" - -json-buffer@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" - integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= - -json-parse-better-errors@^1.0.0, json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== - -json-parse-helpfulerror@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz#13f14ce02eed4e981297b64eb9e3b932e2dd13dc" - integrity sha1-E/FM4C7tTpgSl7ZOueO5MuLdE9w= - dependencies: - jju "^1.1.0" - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= - -json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.0, json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= - -json3@^3.3.2: - version "3.3.3" - resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81" - integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA== - -json5@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" - integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= - -json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== - dependencies: - minimist "^1.2.0" - -json5@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.1.tgz#81b6cb04e9ba496f1c7005d07b4368a2638f90b6" - integrity sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ== - dependencies: - minimist "^1.2.0" - -json5@^2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" - integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== - dependencies: - minimist "^1.2.5" - -jsonfile@^2.1.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" - integrity sha1-NzaitCi4e72gzIO1P6PWM6NcKug= - optionalDependencies: - graceful-fs "^4.1.6" - -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= - optionalDependencies: - graceful-fs "^4.1.6" - -jsonfile@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179" - integrity sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg== - dependencies: - universalify "^1.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - -jsonparse@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" - integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= - -jsonpointer@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" - integrity sha1-T9kss04OnbPInIYi7PUfm5eMbLk= - -jsonschema@^1.0.2: - version "1.2.5" - resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.2.5.tgz#bab69d97fa28946aec0a56a9cc266d23fe80ae61" - integrity sha512-kVTF+08x25PQ0CjuVc0gRM9EUPb0Fe9Ln/utFOgcdxEIOHuU7ooBk/UPTd7t1M91pP35m0MU1T8M5P7vP1bRRw== - -jsonwebtoken@8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.1.0.tgz#c6397cd2e5fd583d65c007a83dc7bb78e6982b83" - integrity sha1-xjl80uX9WD1lwAeoPce7eOaYK4M= - dependencies: - jws "^3.1.4" - 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.0.0" - xtend "^4.0.1" - -jsonwebtoken@^8.2.1, jsonwebtoken@^8.5.1: - version "8.5.1" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" - integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== - dependencies: - jws "^3.2.2" - lodash.includes "^4.3.0" - lodash.isboolean "^3.0.3" - lodash.isinteger "^4.0.4" - lodash.isnumber "^3.0.3" - lodash.isplainobject "^4.0.6" - lodash.isstring "^4.0.1" - lodash.once "^4.0.0" - ms "^2.1.1" - semver "^5.6.0" - -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.2.3" - verror "1.10.0" - -jwa@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" - integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== - dependencies: - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.11" - safe-buffer "^5.0.1" - -jwa@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc" - integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA== - dependencies: - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.11" - safe-buffer "^5.0.1" - -jws@^3.1.4, jws@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" - integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== - dependencies: - jwa "^1.4.1" - safe-buffer "^5.0.1" - -jws@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4" - integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg== - dependencies: - jwa "^2.0.0" - safe-buffer "^5.0.1" - -karma-chrome-launcher@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz#cf1b9d07136cc18fe239327d24654c3dbc368acf" - integrity sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w== - dependencies: - fs-access "^1.0.0" - which "^1.2.1" - -karma-coverage-istanbul-reporter@^2.0.5: - version "2.1.1" - resolved "https://registry.yarnpkg.com/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.1.1.tgz#37a775fbfbb3cbe98cebf19605c94c6277c3b88a" - integrity sha512-CH8lTi8+kKXGvrhy94+EkEMldLCiUA0xMOiL31vvli9qK0T+qcXJAwWBRVJWnVWxYkTmyWar8lPz63dxX6/z1A== - dependencies: - istanbul-api "^2.1.6" - minimatch "^3.0.4" - -karma-jasmine-html-reporter@^1.4.2: - version "1.5.1" - resolved "https://registry.yarnpkg.com/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.5.1.tgz#0fad2d2ac416bc04724318aad29b89bd86f7bfbc" - integrity sha512-LlLqsoGyxT1981z46BRaC1SaY4pTo4EHCA/qZvJEMQXzTtGMyIlmwtxny6FiLO/N/OmZh69eaoNzvBkbHVVFQA== - -karma-jasmine@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/karma-jasmine/-/karma-jasmine-2.0.1.tgz#26e3e31f2faf272dd80ebb0e1898914cc3a19763" - integrity sha512-iuC0hmr9b+SNn1DaUD2QEYtUxkS1J+bSJSn7ejdEexs7P8EYvA1CWkEdrDQ+8jVH3AgWlCNwjYsT1chjcNW9lA== - dependencies: - jasmine-core "^3.3" - -karma-source-map-support@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz#58526ceccf7e8730e56effd97a4de8d712ac0d6b" - integrity sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A== - dependencies: - source-map-support "^0.5.5" - -karma@^4.1.0: - version "4.4.1" - resolved "https://registry.yarnpkg.com/karma/-/karma-4.4.1.tgz#6d9aaab037a31136dc074002620ee11e8c2e32ab" - integrity sha512-L5SIaXEYqzrh6b1wqYC42tNsFMx2PWuxky84pK9coK09MvmL7mxii3G3bZBh/0rvD27lqDd0le9jyhzvwif73A== - dependencies: - bluebird "^3.3.0" - body-parser "^1.16.1" - braces "^3.0.2" - chokidar "^3.0.0" - colors "^1.1.0" - connect "^3.6.0" - di "^0.0.1" - dom-serialize "^2.2.0" - flatted "^2.0.0" - glob "^7.1.1" - graceful-fs "^4.1.2" - http-proxy "^1.13.0" - isbinaryfile "^3.0.0" - lodash "^4.17.14" - log4js "^4.0.0" - mime "^2.3.1" - minimatch "^3.0.2" - optimist "^0.6.1" - qjobs "^1.1.4" - range-parser "^1.2.0" - rimraf "^2.6.0" - safe-buffer "^5.0.1" - socket.io "2.1.1" - source-map "^0.6.1" - tmp "0.0.33" - useragent "2.3.0" - -keyv@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" - integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== - dependencies: - json-buffer "3.0.0" - -killable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" - integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg== - -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -klaw@^1.0.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" - integrity sha1-QIhDO0azsbolnXh4XY6W9zugJDk= - optionalDependencies: - graceful-fs "^4.1.9" - -kuler@1.0.x: - version "1.0.1" - resolved "https://registry.yarnpkg.com/kuler/-/kuler-1.0.1.tgz#ef7c784f36c9fb6e16dd3150d152677b2b0228a6" - integrity sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ== - dependencies: - colornames "^1.1.1" - -latest-version@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" - integrity sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU= - dependencies: - package-json "^4.0.0" - -latest-version@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" - integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== - dependencies: - package-json "^6.3.0" - -lazy-cache@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" - integrity sha1-odePw6UEdMuAhF07O24dpJpEbo4= - -lazystream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" - integrity sha1-9plf4PggOS9hOWvolGJAe7dxaOQ= - dependencies: - readable-stream "^2.0.5" - -lcid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" - integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= - dependencies: - invert-kv "^1.0.0" - -less-loader@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/less-loader/-/less-loader-5.0.0.tgz#498dde3a6c6c4f887458ee9ed3f086a12ad1b466" - integrity sha512-bquCU89mO/yWLaUq0Clk7qCsKhsF/TZpJUzETRvJa9KSVEL9SO3ovCvdEHISBhrC81OwC8QSVX7E0bzElZj9cg== - dependencies: - clone "^2.1.1" - loader-utils "^1.1.0" - pify "^4.0.1" - -less@3.11.3: - version "3.11.3" - resolved "https://registry.yarnpkg.com/less/-/less-3.11.3.tgz#2d853954fcfe0169a8af869620bcaa16563dcc1c" - integrity sha512-VkZiTDdtNEzXA3LgjQiC3D7/ejleBPFVvq+aRI9mIj+Zhmif5TvFPM244bT4rzkvOCvJ9q4zAztok1M7Nygagw== - dependencies: - clone "^2.1.2" - tslib "^1.10.0" - optionalDependencies: - errno "^0.1.1" - graceful-fs "^4.1.2" - image-size "~0.5.0" - make-dir "^2.1.0" - mime "^1.4.1" - promise "^7.1.1" - request "^2.83.0" - source-map "~0.6.0" - -less@^3.10.3: - version "3.10.3" - resolved "https://registry.yarnpkg.com/less/-/less-3.10.3.tgz#417a0975d5eeecc52cff4bcfa3c09d35781e6792" - integrity sha512-vz32vqfgmoxF1h3K4J+yKCtajH0PWmjkIFgbs5d78E/c/e+UQTnI+lWK+1eQRE95PXM2mC3rJlLSSP9VQHnaow== - dependencies: - clone "^2.1.2" - optionalDependencies: - errno "^0.1.1" - graceful-fs "^4.1.2" - image-size "~0.5.0" - mime "^1.4.1" - mkdirp "^0.5.0" - promise "^7.1.1" - request "^2.83.0" - source-map "~0.6.0" - -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== - -levenary@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/levenary/-/levenary-1.1.1.tgz#842a9ee98d2075aa7faeedbe32679e9205f46f77" - integrity sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ== - dependencies: - leven "^3.1.0" - -license-webpack-plugin@2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/license-webpack-plugin/-/license-webpack-plugin-2.1.4.tgz#c5529a4bb87cc9b4489b486d054ba7cae43a554e" - integrity sha512-1Xq72fmPbTg5KofXs+yI5L4QqPFjQ6mZxoeI6D7gfiEDOtaEIk6PGrdLaej90bpDqKNHNxlQ/MW4tMAL6xMPJQ== - dependencies: - "@types/webpack-sources" "^0.1.5" - webpack-sources "^1.2.0" - -lines-and-columns@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" - integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= - -listenercount@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937" - integrity sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc= - -load-json-file@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" - integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA= - dependencies: - graceful-fs "^4.1.2" - parse-json "^2.2.0" - pify "^2.0.0" - pinkie-promise "^2.0.0" - strip-bom "^2.0.0" - -load-json-file@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" - integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= - dependencies: - graceful-fs "^4.1.2" - parse-json "^4.0.0" - pify "^3.0.0" - strip-bom "^3.0.0" - -loader-runner@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" - integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== - -loader-utils@2.0.0, loader-utils@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" - integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^2.1.2" - -loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" - integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== - dependencies: - big.js "^5.2.2" - emojis-list "^2.0.0" - json5 "^1.0.1" - -loader-utils@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" - -locate-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" - integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= - dependencies: - p-locate "^2.0.0" - path-exists "^3.0.0" - -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== - dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -lodash._isnative@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._isnative/-/lodash._isnative-2.4.1.tgz#3ea6404b784a7be836c7b57580e1cdf79b14832c" - integrity sha1-PqZAS3hKe+g2x7V1gOHN95sUgyw= - -lodash._objecttypes@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz#7c0b7f69d98a1f76529f890b0cdb1b4dfec11c11" - integrity sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE= - -lodash._reinterpolate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" - integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= - -lodash._shimkeys@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz#6e9cc9666ff081f0b5a6c978b83e242e6949d203" - integrity sha1-bpzJZm/wgfC1psl4uD4kLmlJ0gM= - dependencies: - lodash._objecttypes "~2.4.1" - -lodash.at@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.at/-/lodash.at-4.6.0.tgz#93cdce664f0a1994ea33dd7cd40e23afd11b0ff8" - integrity sha1-k83OZk8KGZTqM9181A4jr9EbD/g= - -lodash.camelcase@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" - integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= - -lodash.clone@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" - integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= - -lodash.clonedeep@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" - integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= - -lodash.defaults@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" - integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= - -lodash.difference@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" - integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw= - -lodash.flatten@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" - integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= - -lodash.has@^4.5.2: - version "4.5.2" - resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" - integrity sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI= - -lodash.includes@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" - integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= - -lodash.isarguments@2.4.x: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-2.4.1.tgz#4931a9c08253adf091ae7ca192258a973876ecca" - integrity sha1-STGpwIJTrfCRrnyhkiWKlzh27Mo= - -lodash.isarguments@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" - integrity sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo= - -lodash.isboolean@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" - integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= - -lodash.isinteger@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" - integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= - -lodash.isnumber@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" - integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= - -lodash.isobject@^2.4.1, lodash.isobject@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-2.4.1.tgz#5a2e47fe69953f1ee631a7eba1fe64d2d06558f5" - integrity sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU= - dependencies: - lodash._objecttypes "~2.4.1" - -lodash.isobject@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" - integrity sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0= - -lodash.isplainobject@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= - -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= - -lodash.keys@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-2.4.1.tgz#48dea46df8ff7632b10d706b8acb26591e2b3727" - integrity sha1-SN6kbfj/djKxDXBrissmWR4rNyc= - dependencies: - lodash._isnative "~2.4.1" - lodash._shimkeys "~2.4.1" - lodash.isobject "~2.4.1" - -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= - -lodash.once@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" - integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= - -lodash.snakecase@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" - integrity sha1-OdcUo1NXFHg3rv1ktdy7Fr7Nj40= - -lodash.template@^4.0.2: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" - integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.templatesettings "^4.0.0" - -lodash.templatesettings@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" - integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== - dependencies: - lodash._reinterpolate "^3.0.0" - -lodash.toarray@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" - integrity sha1-JMS/zWsvuji/0FlNsRedjptlZWE= - -lodash.union@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" - integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= - -lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= - -lodash.values@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-2.4.1.tgz#abf514436b3cb705001627978cbcf30b1280eea4" - integrity sha1-q/UUQ2s8twUAFieXjLzzCxKA7qQ= - dependencies: - lodash.keys "~2.4.1" - -lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.5.1: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== - -lodash@~2.4.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-2.4.2.tgz#fadd834b9683073da179b3eae6d9c0d15053f73e" - integrity sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4= - -log-symbols@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" - integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== - dependencies: - chalk "^2.0.1" - -log-symbols@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" - integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== - dependencies: - chalk "^2.4.2" - -log4js@^4.0.0: - version "4.5.1" - resolved "https://registry.yarnpkg.com/log4js/-/log4js-4.5.1.tgz#e543625e97d9e6f3e6e7c9fc196dd6ab2cae30b5" - integrity sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw== - dependencies: - date-format "^2.0.0" - debug "^4.1.1" - flatted "^2.0.0" - rfdc "^1.1.4" - streamroller "^1.0.6" - -logform@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/logform/-/logform-2.1.2.tgz#957155ebeb67a13164069825ce67ddb5bb2dd360" - integrity sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ== - dependencies: - colors "^1.2.1" - fast-safe-stringify "^2.0.4" - fecha "^2.3.3" - ms "^2.1.1" - triple-beam "^1.3.0" - -loglevel@^1.6.8: - version "1.6.8" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171" - integrity sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA== - -long@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" - integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== - -long@~3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b" - integrity sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s= - -longest@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" - integrity sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc= - -loose-envify@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" - integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - -loud-rejection@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" - integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= - dependencies: - currently-unhandled "^0.4.1" - signal-exit "^3.0.0" - -lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== - -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" - integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== - -lru-cache@2, lru-cache@^2.5.0: - version "2.7.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" - integrity sha1-bUUk6LlV+V1PW1iFHOId1y+06VI= - -lru-cache@4.1.x, lru-cache@^4.0.1: - version "4.1.5" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" - integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - -lru-cache@^5.0.0, lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -lru-queue@0.1: - version "0.1.0" - resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" - integrity sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM= - dependencies: - es5-ext "~0.10.2" - -lunr@^2.3.8: - version "2.3.8" - resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.8.tgz#a8b89c31f30b5a044b97d2d28e2da191b6ba2072" - integrity sha512-oxMeX/Y35PNFuZoHp+jUj5OSEmLCaIH4KTFJh7a93cHBoFmpw2IoPs22VIz7vyO2YUnx2Tn9dzIwO2P/4quIRg== - -magic-string@0.25.3: - version "0.25.3" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.3.tgz#34b8d2a2c7fec9d9bdf9929a3fd81d271ef35be9" - integrity sha512-6QK0OpF/phMz0Q2AxILkX2mFhi7m+WMwTRg0LQKq/WBB0cDP4rYH3Wp4/d3OTXlrPLVJT/RFqj8tFeAR4nk8AA== - dependencies: - sourcemap-codec "^1.4.4" - -magic-string@0.25.7: - version "0.25.7" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" - integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== - dependencies: - sourcemap-codec "^1.4.4" - -magic-string@^0.25.0, magic-string@^0.25.2: - version "0.25.6" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.6.tgz#5586387d1242f919c6d223579cc938bf1420795e" - integrity sha512-3a5LOMSGoCTH5rbqobC2HuDNRtE2glHZ8J7pK+QZYppyWA36yuNpsX994rIY2nCuyP7CZYy7lQq/X2jygiZ89g== - dependencies: - sourcemap-codec "^1.4.4" - -make-dir@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" - integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== - dependencies: - pify "^3.0.0" - -make-dir@^2.0.0, make-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" - integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== - dependencies: - pify "^4.0.1" - semver "^5.6.0" - -make-dir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.0.tgz#1b5f39f6b9270ed33f9f054c5c0f84304989f801" - integrity sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw== - dependencies: - semver "^6.0.0" - -make-dir@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.2.tgz#04a1acbf22221e1d6ef43559f43e05a90dbb4392" - integrity sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w== - dependencies: - semver "^6.0.0" - -make-fetch-happen@^5.0.0: - version "5.0.2" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-5.0.2.tgz#aa8387104f2687edca01c8687ee45013d02d19bd" - integrity sha512-07JHC0r1ykIoruKO8ifMXu+xEU8qOXDFETylktdug6vJDACnP+HKevOu3PXyNPzFyTSlz8vrBYlBO1JZRe8Cag== - dependencies: - agentkeepalive "^3.4.1" - cacache "^12.0.0" - http-cache-semantics "^3.8.1" - http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.3" - lru-cache "^5.1.1" - mississippi "^3.0.0" - node-fetch-npm "^2.0.2" - promise-retry "^1.1.1" - socks-proxy-agent "^4.0.0" - ssri "^6.0.0" - -mamacro@^0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4" - integrity sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA== - -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - -map-obj@^1.0.0, map-obj@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" - integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= - -map-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-2.0.0.tgz#a65cd29087a92598b8791257a523e021222ac1f9" - integrity sha1-plzSkIepJZi4eRJXpSPgISIqwfk= - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= - dependencies: - object-visit "^1.0.0" - -marked-terminal@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/marked-terminal/-/marked-terminal-3.3.0.tgz#25ce0c0299285998c7636beaefc87055341ba1bd" - integrity sha512-+IUQJ5VlZoAFsM5MHNT7g3RHSkA3eETqhRCdXv4niUMAKHQ7lb1yvAcuGPmm4soxhmtX13u4Li6ZToXtvSEH+A== - dependencies: - ansi-escapes "^3.1.0" - cardinal "^2.1.1" - chalk "^2.4.1" - cli-table "^0.3.1" - node-emoji "^1.4.1" - supports-hyperlinks "^1.0.1" - -marked@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/marked/-/marked-0.7.0.tgz#b64201f051d271b1edc10a04d1ae9b74bb8e5c0e" - integrity sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg== - -marked@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/marked/-/marked-0.8.0.tgz#ec5c0c9b93878dc52dd54be8d0e524097bd81a99" - integrity sha512-MyUe+T/Pw4TZufHkzAfDj6HarCBWia2y27/bhuYkTaiUnfDYFnCP3KUN+9oM7Wi6JA2rymtVYbQu3spE0GCmxQ== - -md5.js@^1.3.4: - version "1.3.5" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" - integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -mdn-data@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" - integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= - -memoizee@^0.4.14: - version "0.4.14" - resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.14.tgz#07a00f204699f9a95c2d9e77218271c7cd610d57" - integrity sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg== - dependencies: - d "1" - es5-ext "^0.10.45" - es6-weak-map "^2.0.2" - event-emitter "^0.3.5" - is-promise "^2.1" - lru-queue "0.1" - next-tick "1" - timers-ext "^0.1.5" - -memory-fs@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" - integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -memory-fs@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" - integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -meow@^3.3.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" - integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= - dependencies: - camelcase-keys "^2.0.0" - decamelize "^1.1.2" - loud-rejection "^1.0.0" - map-obj "^1.0.1" - minimist "^1.1.3" - normalize-package-data "^2.3.4" - object-assign "^4.0.1" - read-pkg-up "^1.0.1" - redent "^1.0.0" - trim-newlines "^1.0.0" - -meow@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/meow/-/meow-4.0.1.tgz#d48598f6f4b1472f35bf6317a95945ace347f975" - integrity sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A== - dependencies: - camelcase-keys "^4.0.0" - decamelize-keys "^1.0.0" - loud-rejection "^1.0.0" - minimist "^1.1.3" - minimist-options "^3.0.1" - normalize-package-data "^2.3.4" - read-pkg-up "^3.0.0" - redent "^2.0.0" - trim-newlines "^2.0.0" - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= - -merge-source-map@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.1.0.tgz#2fdde7e6020939f70906a68f2d7ae685e4c8c646" - integrity sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw== - dependencies: - source-map "^0.6.1" - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -merge2@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= - -micromatch@^3.1.10, micromatch@^3.1.4: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - -micromatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" - integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== - dependencies: - braces "^3.0.1" - picomatch "^2.0.5" - -miller-rabin@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" - integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== - dependencies: - bn.js "^4.0.0" - brorand "^1.0.1" - -mime-db@1.43.0, "mime-db@>= 1.43.0 < 2": - version "1.43.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" - integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== - -mime-db@~1.12.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.12.0.tgz#3d0c63180f458eb10d325aaa37d7c58ae312e9d7" - integrity sha1-PQxjGA9FjrENMlqqN9fFiuMS6dc= - -mime-types@^2.0.8, mime-types@^2.1.12, mime-types@^2.1.16, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: - version "2.1.26" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" - integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== - dependencies: - mime-db "1.43.0" - -mime-types@~2.0.1, mime-types@~2.0.3: - version "2.0.14" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.0.14.tgz#310e159db23e077f8bb22b748dabfa4957140aa6" - integrity sha1-MQ4VnbI+B3+Lsit0jav6SVcUCqY= - dependencies: - mime-db "~1.12.0" - -mime@1.6.0, mime@^1.4.1: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mime@^2.2.0, mime@^2.3.1, mime@^2.4.4: - version "2.4.4" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" - integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA== - -mimic-fn@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" - integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -mimic-response@^1.0.0, mimic-response@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== - -mini-css-extract-plugin@0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz#47f2cf07aa165ab35733b1fc97d4c46c0564339e" - integrity sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A== - dependencies: - loader-utils "^1.1.0" - normalize-url "1.9.1" - schema-utils "^1.0.0" - webpack-sources "^1.1.0" - -minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= - -minimatch@0.3: - version "0.3.0" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.3.0.tgz#275d8edaac4f1bb3326472089e7949c8394699dd" - integrity sha1-J12O2qxPG7MyZHIInnlJyDlGmd0= - dependencies: - lru-cache "2" - sigmund "~1.0.0" - -"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^2.0.1: - version "2.0.10" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-2.0.10.tgz#8d087c39c6b38c001b97fca7ce6d0e1e80afbac7" - integrity sha1-jQh8OcazjAAbl/ynzm0OHoCvusc= - dependencies: - brace-expansion "^1.0.0" - -minimist-options@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-3.0.2.tgz#fba4c8191339e13ecf4d61beb03f070103f3d954" - integrity sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ== - dependencies: - arrify "^1.0.1" - is-plain-obj "^1.1.0" - -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= - -minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= - -minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== - -minimist@~0.0.1: - version "0.0.10" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" - integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= - -minipass-collect@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" - integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== - dependencies: - minipass "^3.0.0" - -minipass-flush@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" - integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== - dependencies: - minipass "^3.0.0" - -minipass-pipeline@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.2.tgz#3dcb6bb4a546e32969c7ad710f2c79a86abba93a" - integrity sha512-3JS5A2DKhD2g0Gg8x3yamO0pj7YeKGwVlDS90pF++kxptwx/F+B//roxf9SqYil5tQo65bijy+dAuAFZmYOouA== - dependencies: - minipass "^3.0.0" - -minipass@^2.3.5, minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" - integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - -minipass@^3.0.0, minipass@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.1.tgz#7607ce778472a185ad6d89082aa2070f79cedcd5" - integrity sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w== - dependencies: - yallist "^4.0.0" - -minizlib@^1.2.1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" - integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== - dependencies: - minipass "^2.9.0" - -minizlib@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.0.tgz#fd52c645301ef09a63a2c209697c294c6ce02cf3" - integrity sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA== - dependencies: - minipass "^3.0.0" - yallist "^4.0.0" - -mississippi@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" - integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA== - dependencies: - concat-stream "^1.5.0" - duplexify "^3.4.2" - end-of-stream "^1.1.0" - flush-write-stream "^1.0.0" - from2 "^2.1.0" - parallel-transform "^1.1.0" - pump "^3.0.0" - pumpify "^1.3.3" - stream-each "^1.1.0" - through2 "^2.0.0" - -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1, mkdirp@~0.5.x: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= - dependencies: - minimist "0.0.8" - -mkdirp@^1.0.3, mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - -modify-values@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" - integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== - -moment@^2.11.2: - version "2.24.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" - integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== - -morgan@^1.8.2: - version "1.9.1" - resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.9.1.tgz#0a8d16734a1d9afbc824b99df87e738e58e2da59" - integrity sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA== - dependencies: - basic-auth "~2.0.0" - debug "2.6.9" - depd "~1.1.2" - on-finished "~2.3.0" - on-headers "~1.0.1" - -move-concurrently@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" - integrity sha1-viwAX9oy4LKa8fBdfEszIUxwH5I= - dependencies: - aproba "^1.1.1" - copy-concurrently "^1.0.0" - fs-write-stream-atomic "^1.0.8" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.3" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -ms@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== - -ms@^2.0.0, ms@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -multicast-dns-service-types@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" - integrity sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE= - -multicast-dns@^6.0.1: - version "6.2.3" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229" - integrity sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g== - dependencies: - dns-packet "^1.3.1" - thunky "^1.0.2" - -mute-stream@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" - integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= - -mute-stream@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" - integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== - -nan@^2.0.5, nan@^2.12.1, nan@^2.13.2: - version "2.14.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" - integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== - -nan@~2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.4.0.tgz#fb3c59d45fe4effe215f0b890f8adf6eb32d2232" - integrity sha1-+zxZ1F/k7/4hXwuJD4rfbrMtIjI= - -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -nash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/nash/-/nash-3.0.0.tgz#bced3a0cb8434c2ad30d1a0d567cfc0c37128eea" - integrity sha512-M5SahEycXUmko3zOvsBkF6p94CWLhnyy9hfpQ9Qzp+rQkQ8D1OaTlfTl1OBWktq9Fak3oDXKU+ev7tiMaMu+1w== - dependencies: - async "^1.3.0" - flat-arguments "^1.0.0" - lodash "^4.17.5" - minimist "^1.1.0" - -ncp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" - integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M= - -needle@^2.2.1: - version "2.4.0" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c" - integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg== - dependencies: - debug "^3.2.6" - iconv-lite "^0.4.4" - sax "^1.2.4" - -negotiator@0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" - integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== - -neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" - integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== - -next-tick@1, next-tick@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" - integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= - -"ng-packagr@^9.0.0 || ^10.0.0": - version "10.0.0" - resolved "https://registry.yarnpkg.com/ng-packagr/-/ng-packagr-10.0.0.tgz#e6fd0d2e144cf92de92ba4d7d37b9da0bdb131f1" - integrity sha512-g+xrHgaF1P8URFb2JZZ4TpTD1uSN1JCHyiP3+PVUp/J8q69QRw9A8dl3JiosgbklUcVif2OnoHTpxNtLcVQ7gw== - dependencies: - "@rollup/plugin-commonjs" "^13.0.0" - "@rollup/plugin-json" "^4.0.0" - "@rollup/plugin-node-resolve" "^8.0.0" - ajv "^6.10.2" - autoprefixer "^9.6.5" - browserslist "^4.7.0" - chalk "^4.0.0" - chokidar "^3.2.1" - commander "^5.0.0" - cssnano-preset-default "^4.0.7" - fs-extra "^9.0.0" - glob "^7.1.2" - injection-js "^2.2.1" - less "^3.10.3" - node-sass-tilde-importer "^1.0.0" - postcss "^7.0.29" - postcss-url "^8.0.0" - read-pkg-up "^5.0.0" - rimraf "^3.0.0" - rollup "^2.8.0" - rollup-plugin-sourcemaps "^0.6.0" - rxjs "^6.5.0" - sass "^1.23.0" - stylus "^0.54.7" - terser "^4.3.8" - update-notifier "^4.0.0" - -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== - -node-emoji@^1.4.1: - version "1.10.0" - resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.10.0.tgz#8886abd25d9c7bb61802a658523d1f8d2a89b2da" - integrity sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw== - dependencies: - lodash.toarray "^4.4.0" - -node-fetch-npm@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz#7258c9046182dca345b4208eda918daf33697ff7" - integrity sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw== - dependencies: - encoding "^0.1.11" - json-parse-better-errors "^1.0.0" - safe-buffer "^5.1.1" - -node-fetch@^1.0.1: - version "1.7.3" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" - integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== - dependencies: - encoding "^0.1.11" - is-stream "^1.0.1" - -node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" - integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== - -node-forge@0.7.4: - version "0.7.4" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.4.tgz#8e6e9f563a1e32213aa7508cded22aa791dbf986" - integrity sha512-8Df0906+tq/omxuCZD6PqhPaQDYuyJ1d+VITgxoIA8zvQd1ru+nMJcDChHH324MWitIgbVkAkQoGEEVJNpn/PA== - -node-forge@0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" - integrity sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ== - -node-forge@^0.9.0: - version "0.9.1" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.1.tgz#775368e6846558ab6676858a4d8c6e8d16c677b5" - integrity sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ== - -node-gyp-build@~3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.7.0.tgz#daa77a4f547b9aed3e2aac779eaf151afd60ec8d" - integrity sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w== - -node-libs-browser@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" - integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== - dependencies: - assert "^1.1.1" - browserify-zlib "^0.2.0" - buffer "^4.3.0" - console-browserify "^1.1.0" - constants-browserify "^1.0.0" - crypto-browserify "^3.11.0" - domain-browser "^1.1.1" - events "^3.0.0" - https-browserify "^1.0.0" - os-browserify "^0.3.0" - path-browserify "0.0.1" - process "^0.11.10" - punycode "^1.2.4" - querystring-es3 "^0.2.0" - readable-stream "^2.3.3" - stream-browserify "^2.0.1" - stream-http "^2.7.2" - string_decoder "^1.0.0" - timers-browserify "^2.0.4" - tty-browserify "0.0.0" - url "^0.11.0" - util "^0.11.0" - vm-browserify "^1.0.1" - -node-pre-gyp@^0.14.0: - version "0.14.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83" - integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA== - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4.4.2" - -node-releases@^1.1.47: - version "1.1.47" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.47.tgz#c59ef739a1fd7ecbd9f0b7cf5b7871e8a8b591e4" - integrity sha512-k4xjVPx5FpwBUj0Gw7uvFOTF4Ep8Hok1I6qjwL3pLfwe7Y0REQSAqOwwv9TWBCUtMHxcXfY4PgRLRozcChvTcA== - dependencies: - semver "^6.3.0" - -node-releases@^1.1.53: - version "1.1.53" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.53.tgz#2d821bfa499ed7c5dffc5e2f28c88e78a08ee3f4" - integrity sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ== - -node-sass-tilde-importer@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/node-sass-tilde-importer/-/node-sass-tilde-importer-1.0.2.tgz#1a15105c153f648323b4347693fdb0f331bad1ce" - integrity sha512-Swcmr38Y7uB78itQeBm3mThjxBy9/Ah/ykPIaURY/L6Nec9AyRoL/jJ7ECfMR+oZeCTVQNxVMu/aHU+TLRVbdg== - dependencies: - find-parent-dir "^0.3.0" - -node-uuid@~1.4.0: - version "1.4.8" - resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.8.tgz#b040eb0923968afabf8d32fb1f17f1167fdab907" - integrity sha1-sEDrCSOWivq/jTL7HxfxFn/auQc= - -nopt@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" - integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= - dependencies: - abbrev "1" - osenv "^0.1.4" - -normalize-package-data@^2.0.0, normalize-package-data@^2.3.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.3.5, normalize-package-data@^2.4.0, normalize-package-data@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= - dependencies: - remove-trailing-separator "^1.0.1" - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -normalize-range@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" - integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= - -normalize-url@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" - integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw= - dependencies: - object-assign "^4.0.1" - prepend-http "^1.0.0" - query-string "^4.1.0" - sort-keys "^1.0.0" - -normalize-url@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" - integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== - -normalize-url@^4.1.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" - integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== - -npm-bundled@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b" - integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA== - dependencies: - npm-normalize-package-bin "^1.0.1" - -npm-install-checks@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-4.0.0.tgz#a37facc763a2fde0497ef2c6d0ac7c3fbe00d7b4" - integrity sha512-09OmyDkNLYwqKPOnbI8exiOZU2GVVmQp7tgez2BPi5OZC8M82elDAps7sxC4l//uSUtotWqoEIDwjRvWH4qz8w== - dependencies: - semver "^7.1.1" - -npm-normalize-package-bin@^1.0.0, npm-normalize-package-bin@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" - integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== - -npm-package-arg@8.0.1, npm-package-arg@^8.0.0: - version "8.0.1" - resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-8.0.1.tgz#9d76f8d7667b2373ffda60bb801a27ef71e3e270" - integrity sha512-/h5Fm6a/exByzFSTm7jAyHbgOqErl9qSNJDQF32Si/ZzgwT2TERVxRxn3Jurw1wflgyVVAxnFR4fRHPM7y1ClQ== - dependencies: - hosted-git-info "^3.0.2" - semver "^7.0.0" - validate-npm-package-name "^3.0.0" - -npm-package-arg@^6.0.0, npm-package-arg@^6.1.0: - version "6.1.1" - resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-6.1.1.tgz#02168cb0a49a2b75bf988a28698de7b529df5cb7" - integrity sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg== - dependencies: - hosted-git-info "^2.7.1" - osenv "^0.1.5" - semver "^5.6.0" - validate-npm-package-name "^3.0.0" - -npm-packlist@^1.1.12, npm-packlist@^1.1.6: - version "1.4.8" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" - integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - npm-normalize-package-bin "^1.0.1" - -npm-pick-manifest@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-6.0.0.tgz#bfde7abe95f2670aed1629a3c18245ccb3cc2eb8" - integrity sha512-PdJpXMvjqt4nftNEDpCgjBUF8yI3Q3MyuAmVB9nemnnCg32F4BPL/JFBfdj8DubgHCYUFQhtLWmBPvdsFtjWMg== - dependencies: - npm-install-checks "^4.0.0" - npm-package-arg "^8.0.0" - semver "^7.0.0" - -npm-pick-manifest@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-2.2.3.tgz#32111d2a9562638bb2c8f2bf27f7f3092c8fae40" - integrity sha512-+IluBC5K201+gRU85vFlUwX3PFShZAbAgDNp2ewJdWMVSppdo/Zih0ul2Ecky/X7b51J7LrrUAP+XOmOCvYZqA== - dependencies: - figgy-pudding "^3.5.1" - npm-package-arg "^6.0.0" - semver "^5.4.1" - -npm-pick-manifest@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-3.0.2.tgz#f4d9e5fd4be2153e5f4e5f9b7be8dc419a99abb7" - integrity sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw== - dependencies: - figgy-pudding "^3.5.1" - npm-package-arg "^6.0.0" - semver "^5.4.1" - -npm-registry-fetch@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-4.0.2.tgz#2b1434f93ccbe6b6385f8e45f45db93e16921d7a" - integrity sha512-Z0IFtPEozNdeZRPh3aHHxdG+ZRpzcbQaJLthsm3VhNf6DScicTFRHZzK82u8RsJUsUHkX+QH/zcB/5pmd20H4A== - dependencies: - JSONStream "^1.3.4" - bluebird "^3.5.1" - figgy-pudding "^3.4.1" - lru-cache "^5.1.1" - make-fetch-happen "^5.0.0" - npm-package-arg "^6.1.0" - safe-buffer "^5.2.0" - -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= - dependencies: - path-key "^2.0.0" - -npmlog@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - -nth-check@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" - integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== - dependencies: - boolbase "~1.0.0" - -null-check@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/null-check/-/null-check-1.0.0.tgz#977dffd7176012b9ec30d2a39db5cf72a0439edd" - integrity sha1-l33/1xdgErnsMNKjnbXPcqBDnt0= - -num2fraction@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" - integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= - -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - -oauth-sign@~0.8.0: - version "0.8.2" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" - integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM= - -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - -object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -object-component@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" - integrity sha1-8MaapQ78lbhmwYb0AKM3acsvEpE= - -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - -object-inspect@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" - integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== - -object-is@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.2.tgz#6b80eb84fe451498f65007982f035a5b445edec4" - integrity sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ== - -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - -object.assign@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - -object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" - integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - dependencies: - isobject "^3.0.1" - -object.values@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" - integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - function-bind "^1.1.1" - has "^1.0.3" - -obuf@^1.0.0, obuf@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" - integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== - -on-finished@^2.2.0, on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= - dependencies: - ee-first "1.1.1" - -on-headers@^1.0.0, on-headers@~1.0.1, on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -one-time@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/one-time/-/one-time-0.0.4.tgz#f8cdf77884826fe4dff93e3a9cc37b1e4480742e" - integrity sha1-+M33eISCb+Tf+T46nMN7HkSAdC4= - -onetime@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" - integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= - dependencies: - mimic-fn "^1.0.0" - -onetime@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5" - integrity sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q== - dependencies: - mimic-fn "^2.1.0" - -open@7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/open/-/open-7.0.3.tgz#db551a1af9c7ab4c7af664139930826138531c48" - integrity sha512-sP2ru2v0P290WFfv49Ap8MF6PkzGNnGlAwHweB4WR4mr5d2d0woiCluUeJ218w7/+PmoBy9JmYgD5A4mLcWOFA== - dependencies: - is-docker "^2.0.0" - is-wsl "^2.1.1" - -open@^6.3.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/open/-/open-6.4.0.tgz#5c13e96d0dc894686164f18965ecfe889ecfc8a9" - integrity sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg== - dependencies: - is-wsl "^1.1.0" - -open@^7.0.3: - version "7.0.4" - resolved "https://registry.yarnpkg.com/open/-/open-7.0.4.tgz#c28a9d315e5c98340bf979fdcb2e58664aa10d83" - integrity sha512-brSA+/yq+b08Hsr4c8fsEW2CRzk1BmfN3SAK/5VCHQ9bdoZJ4qa/+AfR0xHjlbbZUyPkUHs1b8x1RqdyZdkVqQ== - dependencies: - is-docker "^2.0.0" - is-wsl "^2.1.1" - -opencollective-postinstall@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" - integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q== - -opn@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" - integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== - dependencies: - is-wsl "^1.1.0" - -optimist@^0.6.1, optimist@~0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" - integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY= - dependencies: - minimist "~0.0.1" - wordwrap "~0.0.2" - -options@>=0.0.5: - version "0.0.6" - resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f" - integrity sha1-7CLTEoBrtT5zF3Pnza788cZDEo8= - -optjs@~3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/optjs/-/optjs-3.2.2.tgz#69a6ce89c442a44403141ad2f9b370bd5bb6f4ee" - integrity sha1-aabOicRCpEQDFBrS+bNwvVu29O4= - -ora@4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/ora/-/ora-4.0.3.tgz#752a1b7b4be4825546a7a3d59256fa523b6b6d05" - integrity sha512-fnDebVFyz309A73cqCipVL1fBZewq4vwgSHfxh43vVy31mbyoQ8sCH3Oeaog/owYOs/lLlGVPCISQonTneg6Pg== - dependencies: - chalk "^3.0.0" - cli-cursor "^3.1.0" - cli-spinners "^2.2.0" - is-interactive "^1.0.0" - log-symbols "^3.0.0" - mute-stream "0.0.8" - strip-ansi "^6.0.0" - wcwidth "^1.0.1" - -ora@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318" - integrity sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg== - dependencies: - chalk "^2.4.2" - cli-cursor "^2.1.0" - cli-spinners "^2.0.0" - log-symbols "^2.2.0" - strip-ansi "^5.2.0" - wcwidth "^1.0.1" - -original@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" - integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg== - dependencies: - url-parse "^1.4.3" - -os-browserify@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" - integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= - -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - -os-locale@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" - integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= - dependencies: - lcid "^1.0.0" - -os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -osenv@^0.1.4, osenv@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - -p-cancelable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" - integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== - -p-defer@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-3.0.0.tgz#d1dceb4ee9b2b604b1d94ffec83760175d4e6f83" - integrity sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw== - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= - -p-limit@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" - integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== - dependencies: - p-try "^1.0.0" - -p-limit@^2.0.0, p-limit@^2.2.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" - integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== - dependencies: - p-try "^2.0.0" - -p-limit@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-locate@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" - integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= - dependencies: - p-limit "^1.1.0" - -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== - dependencies: - p-limit "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-map@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" - integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== - -p-map@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" - integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== - dependencies: - aggregate-error "^3.0.0" - -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== - dependencies: - aggregate-error "^3.0.0" - -p-retry@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328" - integrity sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w== - dependencies: - retry "^0.12.0" - -p-try@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" - integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -package-json@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" - integrity sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0= - dependencies: - got "^6.7.1" - registry-auth-token "^3.0.1" - registry-url "^3.0.3" - semver "^5.1.0" - -package-json@^6.3.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" - integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== - dependencies: - got "^9.6.0" - registry-auth-token "^4.0.0" - registry-url "^5.0.0" - semver "^6.2.0" - -pacote@9.5.12: - version "9.5.12" - resolved "https://registry.yarnpkg.com/pacote/-/pacote-9.5.12.tgz#1e11dd7a8d736bcc36b375a9804d41bb0377bf66" - integrity sha512-BUIj/4kKbwWg4RtnBncXPJd15piFSVNpTzY0rysSr3VnMowTYgkGKcaHrbReepAkjTr8lH2CVWRi58Spg2CicQ== - dependencies: - bluebird "^3.5.3" - cacache "^12.0.2" - chownr "^1.1.2" - figgy-pudding "^3.5.1" - get-stream "^4.1.0" - glob "^7.1.3" - infer-owner "^1.0.4" - lru-cache "^5.1.1" - make-fetch-happen "^5.0.0" - minimatch "^3.0.4" - minipass "^2.3.5" - mississippi "^3.0.0" - mkdirp "^0.5.1" - normalize-package-data "^2.4.0" - npm-normalize-package-bin "^1.0.0" - npm-package-arg "^6.1.0" - npm-packlist "^1.1.12" - npm-pick-manifest "^3.0.0" - npm-registry-fetch "^4.0.0" - osenv "^0.1.5" - promise-inflight "^1.0.1" - promise-retry "^1.1.1" - protoduck "^5.0.1" - rimraf "^2.6.2" - safe-buffer "^5.1.2" - semver "^5.6.0" - ssri "^6.0.1" - tar "^4.4.10" - unique-filename "^1.1.1" - which "^1.3.1" - -pacote@9.5.5: - version "9.5.5" - resolved "https://registry.yarnpkg.com/pacote/-/pacote-9.5.5.tgz#63355a393614c3424e735820c3731e2cbbedaeeb" - integrity sha512-jAEP+Nqj4kyMWyNpfTU/Whx1jA7jEc5cCOlurm0/0oL+v8TAp1QSsK83N7bYe+2bEdFzMAtPG5TBebjzzGV0cA== - dependencies: - bluebird "^3.5.3" - cacache "^12.0.2" - figgy-pudding "^3.5.1" - get-stream "^4.1.0" - glob "^7.1.3" - infer-owner "^1.0.4" - lru-cache "^5.1.1" - make-fetch-happen "^5.0.0" - minimatch "^3.0.4" - minipass "^2.3.5" - mississippi "^3.0.0" - mkdirp "^0.5.1" - normalize-package-data "^2.4.0" - npm-package-arg "^6.1.0" - npm-packlist "^1.1.12" - npm-pick-manifest "^2.2.3" - npm-registry-fetch "^4.0.0" - osenv "^0.1.5" - promise-inflight "^1.0.1" - promise-retry "^1.1.1" - protoduck "^5.0.1" - rimraf "^2.6.2" - safe-buffer "^5.1.2" - semver "^5.6.0" - ssri "^6.0.1" - tar "^4.4.8" - unique-filename "^1.1.1" - which "^1.3.1" - -pako@~1.0.5: - version "1.0.11" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" - integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== - -parallel-transform@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" - integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg== - dependencies: - cyclist "^1.0.1" - inherits "^2.0.3" - readable-stream "^2.1.5" - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-asn1@^5.0.0: - version "5.1.5" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e" - integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ== - dependencies: - asn1.js "^4.0.0" - browserify-aes "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.0" - pbkdf2 "^3.0.3" - safe-buffer "^5.1.1" - -parse-github-repo-url@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz#9e7d8bb252a6cb6ba42595060b7bf6df3dbc1f50" - integrity sha1-nn2LslKmy2ukJZUGC3v23z28H1A= - -parse-json@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" - integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= - dependencies: - error-ex "^1.2.0" - -parse-json@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" - integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= - dependencies: - error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" - -parse-json@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.0.0.tgz#73e5114c986d143efa3712d4ea24db9a4266f60f" - integrity sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" - lines-and-columns "^1.1.6" - -parse5@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" - integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA== - -parse5@^5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" - integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== - -parseqs@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" - integrity sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0= - dependencies: - better-assert "~1.0.0" - -parseuri@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a" - integrity sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo= - dependencies: - better-assert "~1.0.0" - -parseurl@~1.3.2, parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - -path-browserify@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" - integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== - -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= - -path-exists@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" - integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= - dependencies: - pinkie-promise "^2.0.0" - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-is-inside@^1.0.1, path-is-inside@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= - -path-key@^2.0.0, path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= - -path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== - -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= - -path-to-regexp@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== - dependencies: - isarray "0.0.1" - -path-type@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" - integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE= - dependencies: - graceful-fs "^4.1.2" - pify "^2.0.0" - pinkie-promise "^2.0.0" - -path-type@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" - integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== - dependencies: - pify "^3.0.0" - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -pbkdf2@^3.0.3: - version "3.0.17" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" - integrity sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA== - dependencies: - create-hash "^1.1.2" - create-hmac "^1.1.4" - ripemd160 "^2.0.1" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= - -picomatch@^2.0.4, picomatch@^2.0.7: - version "2.2.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a" - integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA== - -picomatch@^2.0.5, picomatch@^2.2.1, picomatch@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" - integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== - -pify@^2.0.0, pify@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= - -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= - -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" - integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== - -pinkie-promise@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" - integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= - dependencies: - pinkie "^2.0.0" - -pinkie@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" - integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= - -pkg-dir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" - integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== - dependencies: - find-up "^3.0.0" - -pkg-dir@^4.1.0, pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -pkg-up@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f" - integrity sha1-yBmscoBZpGHKscOImivjxJoATX8= - dependencies: - find-up "^2.1.0" - -please-upgrade-node@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" - integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg== - dependencies: - semver-compare "^1.0.0" - -plist@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.1.tgz#a9b931d17c304e8912ef0ba3bdd6182baf2e1f8c" - integrity sha512-GpgvHHocGRyQm74b6FWEZZVRroHKE1I0/BTjAmySaohK+cUn+hZpbqXkc3KWgW3gQYkqcQej35FohcT0FRlkRQ== - dependencies: - base64-js "^1.2.3" - xmlbuilder "^9.0.7" - xmldom "0.1.x" - -portfinder@^1.0.23: - version "1.0.25" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.25.tgz#254fd337ffba869f4b9d37edc298059cb4d35eca" - integrity sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg== - dependencies: - async "^2.6.2" - debug "^3.1.1" - mkdirp "^0.5.1" - -portfinder@^1.0.26: - version "1.0.26" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.26.tgz#475658d56ca30bed72ac7f1378ed350bd1b64e70" - integrity sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ== - dependencies: - async "^2.6.2" - debug "^3.1.1" - mkdirp "^0.5.1" - -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= - -postcss-calc@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.1.tgz#36d77bab023b0ecbb9789d84dcb23c4941145436" - integrity sha512-oXqx0m6tb4N3JGdmeMSc/i91KppbYsFZKdH0xMOqK8V1rJlzrKlTdokz8ozUXLVejydRN6u2IddxpcijRj2FqQ== - dependencies: - css-unit-converter "^1.1.1" - postcss "^7.0.5" - postcss-selector-parser "^5.0.0-rc.4" - postcss-value-parser "^3.3.1" - -postcss-colormin@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381" - integrity sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw== - dependencies: - browserslist "^4.0.0" - color "^3.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-convert-values@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz#ca3813ed4da0f812f9d43703584e449ebe189a7f" - integrity sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ== - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-discard-comments@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033" - integrity sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg== - dependencies: - postcss "^7.0.0" - -postcss-discard-duplicates@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz#3fe133cd3c82282e550fc9b239176a9207b784eb" - integrity sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ== - dependencies: - postcss "^7.0.0" - -postcss-discard-empty@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz#c8c951e9f73ed9428019458444a02ad90bb9f765" - integrity sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w== - dependencies: - postcss "^7.0.0" - -postcss-discard-overridden@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz#652aef8a96726f029f5e3e00146ee7a4e755ff57" - integrity sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg== - dependencies: - postcss "^7.0.0" - -postcss-import@12.0.1: - version "12.0.1" - resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-12.0.1.tgz#cf8c7ab0b5ccab5649024536e565f841928b7153" - integrity sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw== - dependencies: - postcss "^7.0.1" - postcss-value-parser "^3.2.3" - read-cache "^1.0.0" - resolve "^1.1.7" - -postcss-load-config@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.0.tgz#c84d692b7bb7b41ddced94ee62e8ab31b417b003" - integrity sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q== - dependencies: - cosmiconfig "^5.0.0" - import-cwd "^2.0.0" - -postcss-loader@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-3.0.0.tgz#6b97943e47c72d845fa9e03f273773d4e8dd6c2d" - integrity sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA== - dependencies: - loader-utils "^1.1.0" - postcss "^7.0.0" - postcss-load-config "^2.0.0" - schema-utils "^1.0.0" - -postcss-merge-longhand@^4.0.11: - version "4.0.11" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24" - integrity sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw== - dependencies: - css-color-names "0.0.4" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - stylehacks "^4.0.0" - -postcss-merge-rules@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz#362bea4ff5a1f98e4075a713c6cb25aefef9a650" - integrity sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ== - dependencies: - browserslist "^4.0.0" - caniuse-api "^3.0.0" - cssnano-util-same-parent "^4.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" - vendors "^1.0.0" - -postcss-minify-font-values@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz#cd4c344cce474343fac5d82206ab2cbcb8afd5a6" - integrity sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg== - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-minify-gradients@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz#93b29c2ff5099c535eecda56c4aa6e665a663471" - integrity sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q== - dependencies: - cssnano-util-get-arguments "^4.0.0" - is-color-stop "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-minify-params@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz#6b9cef030c11e35261f95f618c90036d680db874" - integrity sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg== - dependencies: - alphanum-sort "^1.0.0" - browserslist "^4.0.0" - cssnano-util-get-arguments "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - uniqs "^2.0.0" - -postcss-minify-selectors@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz#e2e5eb40bfee500d0cd9243500f5f8ea4262fbd8" - integrity sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g== - dependencies: - alphanum-sort "^1.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" - -postcss-modules-extract-imports@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e" - integrity sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ== - dependencies: - postcss "^7.0.5" - -postcss-modules-local-by-default@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz#e8a6561be914aaf3c052876377524ca90dbb7915" - integrity sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ== - dependencies: - icss-utils "^4.1.1" - postcss "^7.0.16" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.0.0" - -postcss-modules-scope@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz#385cae013cc7743f5a7d7602d1073a89eaae62ee" - integrity sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ== - dependencies: - postcss "^7.0.6" - postcss-selector-parser "^6.0.0" - -postcss-modules-values@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10" - integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg== - dependencies: - icss-utils "^4.0.0" - postcss "^7.0.6" - -postcss-normalize-charset@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4" - integrity sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g== - dependencies: - postcss "^7.0.0" - -postcss-normalize-display-values@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz#0dbe04a4ce9063d4667ed2be476bb830c825935a" - integrity sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ== - dependencies: - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-positions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz#05f757f84f260437378368a91f8932d4b102917f" - integrity sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA== - dependencies: - cssnano-util-get-arguments "^4.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-repeat-style@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz#c4ebbc289f3991a028d44751cbdd11918b17910c" - integrity sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q== - dependencies: - cssnano-util-get-arguments "^4.0.0" - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-string@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz#cd44c40ab07a0c7a36dc5e99aace1eca4ec2690c" - integrity sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA== - dependencies: - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-timing-functions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz#8e009ca2a3949cdaf8ad23e6b6ab99cb5e7d28d9" - integrity sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A== - dependencies: - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-unicode@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz#841bd48fdcf3019ad4baa7493a3d363b52ae1cfb" - integrity sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg== - dependencies: - browserslist "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-url@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz#10e437f86bc7c7e58f7b9652ed878daaa95faae1" - integrity sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA== - dependencies: - is-absolute-url "^2.0.0" - normalize-url "^3.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-whitespace@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82" - integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA== - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-ordered-values@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee" - integrity sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw== - dependencies: - cssnano-util-get-arguments "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-reduce-initial@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df" - integrity sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA== - dependencies: - browserslist "^4.0.0" - caniuse-api "^3.0.0" - has "^1.0.0" - postcss "^7.0.0" - -postcss-reduce-transforms@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz#17efa405eacc6e07be3414a5ca2d1074681d4e29" - integrity sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg== - dependencies: - cssnano-util-get-match "^4.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-selector-parser@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz#4f875f4afb0c96573d5cf4d74011aee250a7e865" - integrity sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU= - dependencies: - dot-prop "^4.1.1" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss-selector-parser@^5.0.0-rc.4: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c" - integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ== - dependencies: - cssesc "^2.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c" - integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== - dependencies: - cssesc "^3.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss-svgo@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.2.tgz#17b997bc711b333bab143aaed3b8d3d6e3d38258" - integrity sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw== - dependencies: - is-svg "^3.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - svgo "^1.0.0" - -postcss-unique-selectors@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz#9446911f3289bfd64c6d680f073c03b1f9ee4bac" - integrity sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg== - dependencies: - alphanum-sort "^1.0.0" - postcss "^7.0.0" - uniqs "^2.0.0" - -postcss-url@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/postcss-url/-/postcss-url-8.0.0.tgz#7b10059bd12929cdbb1971c60f61a0e5af86b4ca" - integrity sha512-E2cbOQ5aii2zNHh8F6fk1cxls7QVFZjLPSrqvmiza8OuXLzIpErij8BDS5Y3STPfJgpIMNCPEr8JlKQWEoozUw== - dependencies: - mime "^2.3.1" - minimatch "^3.0.4" - mkdirp "^0.5.0" - postcss "^7.0.2" - xxhashjs "^0.2.1" - -postcss-value-parser@^3.0.0, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" - integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== - -postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.3: - version "4.1.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" - integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== - -postcss-value-parser@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz#482282c09a42706d1fc9a069b73f44ec08391dc9" - integrity sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ== - -postcss@7.0.27: - version "7.0.27" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.27.tgz#cc67cdc6b0daa375105b7c424a85567345fc54d9" - integrity sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ== - dependencies: - chalk "^2.4.2" - source-map "^0.6.1" - supports-color "^6.1.0" - -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.2, postcss@^7.0.26, postcss@^7.0.5: - version "7.0.26" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.26.tgz#5ed615cfcab35ba9bbb82414a4fa88ea10429587" - integrity sha512-IY4oRjpXWYshuTDFxMVkJDtWIk2LhsTlu8bZnbEJA4+bYT16Lvpo8Qv6EvDumhYRgzjZl489pmsY3qVgJQ08nA== - dependencies: - chalk "^2.4.2" - source-map "^0.6.1" - supports-color "^6.1.0" - -postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.27, postcss@^7.0.29, postcss@^7.0.6: - version "7.0.32" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d" - integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw== - dependencies: - chalk "^2.4.2" - source-map "^0.6.1" - supports-color "^6.1.0" - -prepend-http@^1.0.0, prepend-http@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" - integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= - -prepend-http@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" - integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= - -pretty-size@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pretty-size/-/pretty-size-2.0.0.tgz#30abccd0e4c9c02007e2d4646a1557d3ae6a66fd" - integrity sha1-MKvM0OTJwCAH4tRkahVX065qZv0= - -private@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" - integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== - -process-nextick-args@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" - integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -process@^0.11.10: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= - -progress@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - -promise-inflight@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" - integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= - -promise-polyfill@8.1.3: - version "8.1.3" - resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.1.3.tgz#8c99b3cf53f3a91c68226ffde7bde81d7f904116" - integrity sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g== - -promise-retry@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-1.1.1.tgz#6739e968e3051da20ce6497fb2b50f6911df3d6d" - integrity sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0= - dependencies: - err-code "^1.0.0" - retry "^0.10.0" - -promise@^7.1.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" - integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== - dependencies: - asap "~2.0.3" - -protobufjs@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-5.0.3.tgz#e4dfe9fb67c90b2630d15868249bcc4961467a17" - integrity sha512-55Kcx1MhPZX0zTbVosMQEO5R6/rikNXd9b6RQK4KSPcrSIIwoXTtebIczUrXlwaSrbz4x8XUVThGPob1n8I4QA== - dependencies: - ascli "~1" - bytebuffer "~5" - glob "^7.0.5" - yargs "^3.10.0" - -protobufjs@^6.8.1, protobufjs@^6.8.6, protobufjs@^6.8.8: - version "6.8.8" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.8.8.tgz#c8b4f1282fd7a90e6f5b109ed11c84af82908e7c" - integrity sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw== - dependencies: - "@protobufjs/aspromise" "^1.1.2" - "@protobufjs/base64" "^1.1.2" - "@protobufjs/codegen" "^2.0.4" - "@protobufjs/eventemitter" "^1.1.0" - "@protobufjs/fetch" "^1.1.0" - "@protobufjs/float" "^1.0.2" - "@protobufjs/inquire" "^1.1.0" - "@protobufjs/path" "^1.1.2" - "@protobufjs/pool" "^1.1.0" - "@protobufjs/utf8" "^1.1.0" - "@types/long" "^4.0.0" - "@types/node" "^10.1.0" - long "^4.0.0" - -protoduck@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/protoduck/-/protoduck-5.0.1.tgz#03c3659ca18007b69a50fd82a7ebcc516261151f" - integrity sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg== - dependencies: - genfun "^5.0.0" - -protractor@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/protractor/-/protractor-3.0.0.tgz#ac76778770f629bab2afa56a6aac9c59ca6d259d" - integrity sha1-rHZ3h3D2Kbqyr6VqaqycWcptJZ0= - dependencies: - adm-zip "0.4.4" - glob "~3.2" - jasmine "2.3.2" - jasminewd2 "0.0.6" - lodash "~2.4.1" - optimist "~0.6.0" - q "1.0.0" - request "~2.57.0" - saucelabs "~1.0.1" - selenium-webdriver "2.48.2" - source-map-support "~0.3.2" - -proxy-addr@~2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34" - integrity sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ== - dependencies: - forwarded "~0.1.2" - ipaddr.js "1.9.0" - -prr@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" - integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= - -pseudomap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= - -psl@^1.1.24, psl@^1.1.28: - version "1.7.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" - integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== - -public-encrypt@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" - integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== - dependencies: - bn.js "^4.1.0" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - parse-asn1 "^5.0.0" - randombytes "^2.0.1" - safe-buffer "^5.1.2" - -pump@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" - integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pumpify@^1.3.3: - version "1.5.1" - resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" - integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== - dependencies: - duplexify "^3.6.0" - inherits "^2.0.3" - pump "^2.0.0" - -pumpify@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-2.0.1.tgz#abfc7b5a621307c728b551decbbefb51f0e4aa1e" - integrity sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw== - dependencies: - duplexify "^4.1.1" - inherits "^2.0.3" - pump "^3.0.0" - -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= - -punycode@^1.2.4, punycode@^1.3.2, punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= - -punycode@^2.1.0, punycode@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -q@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/q/-/q-1.0.0.tgz#dc1f92c4587be54f7853b29dc28e6d243a88498d" - integrity sha1-3B+SxFh75U94U7Kdwo5tJDqISY0= - -q@^1.1.2, q@^1.4.1, q@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" - integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= - -qjobs@^1.1.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071" - integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg== - -qs@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" - integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== - -qs@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-3.1.0.tgz#d0e9ae745233a12dc43fb4f3055bba446261153c" - integrity sha1-0OmudFIzoS3EP7TzBVu6RGJhFTw= - -qs@~6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" - integrity sha1-E+JtKK1rD/qpExLNO/cI7TUecjM= - -qs@~6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== - -query-string@^4.1.0: - version "4.3.4" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" - integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s= - dependencies: - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - -querystring-es3@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" - integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= - -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= - -querystringify@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" - integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== - -quick-lru@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8" - integrity sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g= - -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -randomfill@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" - integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== - dependencies: - randombytes "^2.0.5" - safe-buffer "^5.1.0" - -range-parser@^1.2.0, range-parser@^1.2.1, range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" - integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== - dependencies: - bytes "3.1.0" - http-errors "1.7.2" - iconv-lite "0.4.24" - unpipe "1.0.0" - -raw-loader@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-4.0.0.tgz#d639c40fb9d72b5c7f8abc1fb2ddb25b29d3d540" - integrity sha512-iINUOYvl1cGEmfoaLjnZXt4bKfT2LJnZZib5N/LLyAphC+Dd11vNP9CNVb38j+SAJpFI1uo8j9frmih53ASy7Q== - dependencies: - loader-utils "^1.2.3" - schema-utils "^2.5.0" - -rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -read-cache@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" - integrity sha1-5mTvMRYRZsl1HNvo28+GtftY93Q= - dependencies: - pify "^2.3.0" - -read-package-json@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-2.1.1.tgz#16aa66c59e7d4dad6288f179dd9295fd59bb98f1" - integrity sha512-dAiqGtVc/q5doFz6096CcnXhpYk0ZN8dEKVkGLU0CsASt8SrgF6SF7OTKAYubfvFhWaqofl+Y8HK19GR8jwW+A== - dependencies: - glob "^7.1.1" - json-parse-better-errors "^1.0.1" - normalize-package-data "^2.0.0" - npm-normalize-package-bin "^1.0.0" - optionalDependencies: - graceful-fs "^4.1.2" - -read-package-tree@5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/read-package-tree/-/read-package-tree-5.3.1.tgz#a32cb64c7f31eb8a6f31ef06f9cedf74068fe636" - integrity sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw== - dependencies: - read-package-json "^2.0.0" - readdir-scoped-modules "^1.0.0" - util-promisify "^2.1.0" - -read-pkg-up@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" - integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI= - dependencies: - find-up "^1.0.0" - read-pkg "^1.0.0" - -read-pkg-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" - integrity sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc= - dependencies: - find-up "^2.0.0" - read-pkg "^3.0.0" - -read-pkg-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-5.0.0.tgz#b6a6741cb144ed3610554f40162aa07a6db621b8" - integrity sha512-XBQjqOBtTzyol2CpsQOw8LHV0XbDZVG7xMMjmXAJomlVY03WOBRmYgDJETlvcg0H63AJvPRwT7GFi5rvOzUOKg== - dependencies: - find-up "^3.0.0" - read-pkg "^5.0.0" - -read-pkg@^1.0.0, read-pkg@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" - integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg= - dependencies: - load-json-file "^1.0.0" - normalize-package-data "^2.3.2" - path-type "^1.0.0" - -read-pkg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" - integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= - dependencies: - load-json-file "^4.0.0" - normalize-package-data "^2.3.2" - path-type "^3.0.0" - -read-pkg@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" - integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== - dependencies: - "@types/normalize-package-data" "^2.4.0" - normalize-package-data "^2.5.0" - parse-json "^5.0.0" - type-fest "^0.6.0" - -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== - 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" - -"readable-stream@2 || 3", readable-stream@^3.0.1, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.5.0.tgz#465d70e6d1087f6162d079cd0b5db7fbebfd1606" - integrity sha512-gSz026xs2LfxBPudDuI41V1lka8cxg64E66SGe78zJlsUofOg/yqwezdIcdfwik6B4h8LFmWPA9ef9X3FiNFLA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readable-stream@~1.0.26: - version "1.0.34" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" - integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -readable-stream@~2.0.0: - version "2.0.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" - integrity sha1-j5A0HmilPMySh4jaz80Rs265t44= - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~0.10.x" - util-deprecate "~1.0.1" - -readdir-scoped-modules@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" - integrity sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw== - dependencies: - debuglog "^1.0.1" - dezalgo "^1.0.0" - graceful-fs "^4.1.2" - once "^1.3.0" - -readdirp@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" - integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== - dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" - -readdirp@~3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.3.0.tgz#984458d13a1e42e2e9f5841b129e162f369aff17" - integrity sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ== - dependencies: - picomatch "^2.0.7" - -rechoir@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" - integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q= - dependencies: - resolve "^1.1.6" - -redent@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" - integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94= - dependencies: - indent-string "^2.1.0" - strip-indent "^1.0.1" - -redent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-2.0.0.tgz#c1b2007b42d57eb1389079b3c8333639d5e1ccaa" - integrity sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo= - dependencies: - indent-string "^3.0.0" - strip-indent "^2.0.0" - -redeyed@~2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-2.1.1.tgz#8984b5815d99cb220469c99eeeffe38913e6cc0b" - integrity sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs= - dependencies: - esprima "~4.0.0" - -reflect-metadata@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.2.tgz#ea23e5823dc830f292822bd3da9b89fd57bffb03" - integrity sha1-6iPlgj3IMPKSgivT2puJ/Ve/+wM= - -reflect-metadata@^0.1.2: - version "0.1.13" - resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" - integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== - -regenerate-unicode-properties@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e" - integrity sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA== - dependencies: - regenerate "^1.4.0" - -regenerate-unicode-properties@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" - integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA== - dependencies: - regenerate "^1.4.0" - -regenerate@^1.2.1, regenerate@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" - integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== - -regenerator-runtime@0.13.5, regenerator-runtime@^0.13.4: - version "0.13.5" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" - integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== - -regenerator-runtime@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" - integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== - -regenerator-transform@^0.14.2: - version "0.14.4" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.4.tgz#5266857896518d1616a78a0479337a30ea974cc7" - integrity sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw== - dependencies: - "@babel/runtime" "^7.8.4" - private "^0.1.8" - -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - -regexp.prototype.flags@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75" - integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - -regexpu-core@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b" - integrity sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs= - dependencies: - regenerate "^1.2.1" - regjsgen "^0.2.0" - regjsparser "^0.1.4" - -regexpu-core@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.6.0.tgz#2037c18b327cfce8a6fea2a4ec441f2432afb8b6" - integrity sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg== - dependencies: - regenerate "^1.4.0" - regenerate-unicode-properties "^8.1.0" - regjsgen "^0.5.0" - regjsparser "^0.6.0" - unicode-match-property-ecmascript "^1.0.4" - unicode-match-property-value-ecmascript "^1.1.0" - -regexpu-core@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.0.tgz#fcbf458c50431b0bb7b45d6967b8192d91f3d938" - integrity sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ== - dependencies: - regenerate "^1.4.0" - regenerate-unicode-properties "^8.2.0" - regjsgen "^0.5.1" - regjsparser "^0.6.4" - unicode-match-property-ecmascript "^1.0.4" - unicode-match-property-value-ecmascript "^1.2.0" - -registry-auth-token@^3.0.1: - version "3.4.0" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.4.0.tgz#d7446815433f5d5ed6431cd5dca21048f66b397e" - integrity sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A== - dependencies: - rc "^1.1.6" - safe-buffer "^5.0.1" - -registry-auth-token@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.1.1.tgz#40a33be1e82539460f94328b0f7f0f84c16d9479" - integrity sha512-9bKS7nTl9+/A1s7tnPeGrUpRcVY+LUh7bfFgzpndALdPfXQBfQV77rQVtqgUV3ti4vc/Ik81Ex8UJDWDQ12zQA== - dependencies: - rc "^1.2.8" - -registry-url@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" - integrity sha1-PU74cPc93h138M+aOBQyRE4XSUI= - dependencies: - rc "^1.0.1" - -registry-url@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" - integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== - dependencies: - rc "^1.2.8" - -regjsgen@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" - integrity sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc= - -regjsgen@^0.5.0, regjsgen@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c" - integrity sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg== - -regjsparser@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" - integrity sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw= - dependencies: - jsesc "~0.5.0" - -regjsparser@^0.6.0: - version "0.6.2" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.2.tgz#fd62c753991467d9d1ffe0a9f67f27a529024b96" - integrity sha512-E9ghzUtoLwDekPT0DYCp+c4h+bvuUpe6rRHCTYn6eGoqj1LgKXxT6I0Il4WbjhQkOghzi/V+y03bPKvbllL93Q== - dependencies: - jsesc "~0.5.0" - -regjsparser@^0.6.4: - version "0.6.4" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.4.tgz#a769f8684308401a66e9b529d2436ff4d0666272" - integrity sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw== - dependencies: - jsesc "~0.5.0" - -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= - -repeat-element@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" - integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== - -repeat-string@^1.5.2, repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= - -repeating@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" - integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= - dependencies: - is-finite "^1.0.0" - -replace-in-file@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/replace-in-file/-/replace-in-file-5.0.2.tgz#bd26203b66dfb5b8112ae36a2d2cf928ea4cfe12" - integrity sha512-1Vc7Sbr/rTuHgU1PZuBb7tGsFx3D4NKdhV4BpEF2MuN/6+SoXcFtx+dZ1Zz+5Dq4k5x9js87Y+gXQYPTQ9ppkA== - dependencies: - chalk "^3.0.0" - glob "^7.1.6" - yargs "^15.0.2" - -request@^2.83.0, request@^2.87.0, request@^2.88.0: - version "2.88.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" - integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.0" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.4.3" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - -request@~2.57.0: - version "2.57.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.57.0.tgz#d445105a42d009b9d724289633b449a6d723d989" - integrity sha1-1EUQWkLQCbnXJCiWM7RJptcj2Yk= - dependencies: - aws-sign2 "~0.5.0" - bl "~0.9.0" - caseless "~0.10.0" - combined-stream "~1.0.1" - forever-agent "~0.6.0" - form-data "~0.2.0" - har-validator "^1.6.1" - hawk "~2.3.0" - http-signature "~0.11.0" - isstream "~0.1.1" - json-stringify-safe "~5.0.0" - mime-types "~2.0.1" - node-uuid "~1.4.0" - oauth-sign "~0.8.0" - qs "~3.1.0" - stringstream "~0.0.4" - tough-cookie ">=0.12.0" - tunnel-agent "~0.4.0" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= - -resolve-cwd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" - integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= - dependencies: - resolve-from "^3.0.0" - -resolve-from@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" - integrity sha1-six699nWiBvItuZTM17rywoYh0g= - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= - -resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.11.0, resolve@^1.3.2: - version "1.15.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.0.tgz#1b7ca96073ebb52e741ffd799f6b39ea462c67f5" - integrity sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw== - dependencies: - path-parse "^1.0.6" - -resolve@^1.14.2: - version "1.17.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" - integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== - dependencies: - path-parse "^1.0.6" - -responselike@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" - integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= - dependencies: - lowercase-keys "^1.0.0" - -restore-cursor@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" - integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= - dependencies: - onetime "^2.0.0" - signal-exit "^3.0.2" - -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - -retry-request@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/retry-request/-/retry-request-4.1.1.tgz#f676d0db0de7a6f122c048626ce7ce12101d2bd8" - integrity sha512-BINDzVtLI2BDukjWmjAIRZ0oglnCAkpP2vQjM3jdLhmT62h0xnQgciPwBRDAvHqpkPT2Wo1XuUyLyn6nbGrZQQ== - dependencies: - debug "^4.1.1" - through2 "^3.0.1" - -retry@^0.10.0: - version "0.10.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" - integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= - -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rfdc@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2" - integrity sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug== - -rgb-regex@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" - integrity sha1-wODWiC3w4jviVKR16O3UGRX+rrE= - -rgba-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" - integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= - -right-align@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" - integrity sha1-YTObci/mo1FWiSENJOFMlhSGE+8= - dependencies: - align-text "^0.1.1" - -rimraf@2, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@^2.7.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - -rimraf@3.0.2, rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -rimraf@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.1.tgz#48d3d4cb46c80d388ab26cd61b1b466ae9ae225a" - integrity sha512-IQ4ikL8SjBiEDZfk+DFVwqRK8md24RWMEJkdSlgNLkyyAImcjf8SWvU1qFMDOb4igBClbTQ/ugPqXcRwdFTxZw== - dependencies: - glob "^7.1.3" - -ripemd160@^2.0.0, ripemd160@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" - integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - -rollup-plugin-sourcemaps@^0.6.0: - version "0.6.2" - resolved "https://registry.yarnpkg.com/rollup-plugin-sourcemaps/-/rollup-plugin-sourcemaps-0.6.2.tgz#1eed5a3e07b833dc14c4cdb1e63b300d340f4a74" - integrity sha512-9AwTKg3yRykwzemfLt71ySe0LvrAci+bpsOL1LaTYFk5BX4HF6X7DQfpHa74ANfSja3hyjiQkXCR8goSOnW//Q== - dependencies: - "@rollup/pluginutils" "^3.0.9" - source-map-resolve "^0.6.0" - -rollup@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.1.0.tgz#552e248e397a06b9c6db878c0564ca4ee06729c9" - integrity sha512-gfE1455AEazVVTJoeQtcOq/U6GSxwoj4XPSWVsuWmgIxj7sBQNLDOSA82PbdMe+cP8ql8fR1jogPFe8Wg8g4SQ== - optionalDependencies: - fsevents "~2.1.2" - -rollup@^0.36.3: - version "0.36.4" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.36.4.tgz#a224494c5386c1d73d38f7bb86f69f5eb011a3d2" - integrity sha1-oiRJTFOGwdc9OPe7hvafXrARo9I= - dependencies: - source-map-support "^0.4.0" - -rollup@^2.8.0: - version "2.18.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.18.0.tgz#f03801e5dd01415e5675dcf61c824ea493ca0392" - integrity sha512-LhuQQp3WpnHo3HlKCRrdMXpB6jdLsGOoXXSfMjbv74s5VdV3WZhkYJT0Z6w/EH3UgPH+g/S9T4GJrKW/5iD8TA== - optionalDependencies: - fsevents "~2.1.2" - -router@^1.3.1: - version "1.3.4" - resolved "https://registry.yarnpkg.com/router/-/router-1.3.4.tgz#f4e4a1648eada2e16fd622fc389c90daf7307ed8" - integrity sha512-a5uZFwgKExBZzh4U5jujU05DkImy9/ub8omiU7RlZlNnSF07tjvNJTOgHdZOjKDeUZqlkpz8CjDoz5SY3kimOA== - dependencies: - array-flatten "3.0.0" - debug "2.6.9" - methods "~1.1.2" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - setprototypeof "1.2.0" - utils-merge "1.0.1" - -rsvp@^3.0.13, rsvp@^3.6.2: - version "3.6.2" - resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a" - integrity sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw== - -run-async@^2.2.0, run-async@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" - integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA= - dependencies: - is-promise "^2.1.0" - -run-async@^2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" - integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== - -run-parallel@^1.1.9: - version "1.1.9" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" - integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== - -run-queue@^1.0.0, run-queue@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" - integrity sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec= - dependencies: - aproba "^1.1.1" - -rx@2.3.24: - version "2.3.24" - resolved "https://registry.yarnpkg.com/rx/-/rx-2.3.24.tgz#14f950a4217d7e35daa71bbcbe58eff68ea4b2b7" - integrity sha1-FPlQpCF9fjXapxu8vljv9o6ksrc= - -rxfire@^3.9.7: - version "3.9.9" - resolved "https://registry.yarnpkg.com/rxfire/-/rxfire-3.9.9.tgz#3e97f077d589d931557ef44e1a6665613b5d31c0" - integrity sha512-wym5gfC9J473FO0+A1n1YCwNc92JjC0vE8dorR/RO1IWD5R2LZjeruDTuwFm3/HO9hjV/NTcOpfPbmdhne2jZQ== - dependencies: - tslib "1.10.0" - -rxjs@6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.4.0.tgz#f3bb0fe7bda7fb69deac0c16f17b50b0b8790504" - integrity sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw== - dependencies: - tslib "^1.9.0" - -rxjs@6.5.4, rxjs@^6.4.0, rxjs@^6.5.0, rxjs@^6.5.3: - version "6.5.4" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c" - integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q== - dependencies: - tslib "^1.9.0" - -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" - integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== - -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" - -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@^2.1.2, safer-buffer@~2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sass-loader@8.0.2: - version "8.0.2" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-8.0.2.tgz#debecd8c3ce243c76454f2e8290482150380090d" - integrity sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ== - dependencies: - clone-deep "^4.0.1" - loader-utils "^1.2.3" - neo-async "^2.6.1" - schema-utils "^2.6.1" - semver "^6.3.0" - -sass@1.26.3: - version "1.26.3" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.26.3.tgz#412df54486143b76b5a65cdf7569e86f44659f46" - integrity sha512-5NMHI1+YFYw4sN3yfKjpLuV9B5l7MqQ6FlkTcC4FT+oHbBRUZoSjHrrt/mE0nFXJyY2kQtU9ou9HxvFVjLFuuw== - dependencies: - chokidar ">=2.0.0 <4.0.0" - -sass@^1.23.0: - version "1.25.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.25.0.tgz#f8bd7dfbb39d6b0305e27704a8ebe637820693f3" - integrity sha512-uQMjye0Y70SEDGO56n0j91tauqS9E1BmpKHtiYNQScXDHeaE9uHwNEqQNFf4Bes/3DHMNinB6u79JsG10XWNyw== - dependencies: - chokidar ">=2.0.0 <4.0.0" - -saucelabs@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/saucelabs/-/saucelabs-1.0.1.tgz#b50a100d9c5a4140748b35335a6e5d70017dadf9" - integrity sha1-tQoQDZxaQUB0izUzWm5dcAF9rfk= - dependencies: - https-proxy-agent "^1.0.0" - -sax@0.6.x: - version "0.6.1" - resolved "https://registry.yarnpkg.com/sax/-/sax-0.6.1.tgz#563b19c7c1de892e09bfc4f2fc30e3c27f0952b9" - integrity sha1-VjsZx8HeiS4Jv8Ty/DDjwn8JUrk= - -sax@^1.2.4, sax@~1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -schema-utils@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" - integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== - dependencies: - ajv "^6.1.0" - ajv-errors "^1.0.0" - ajv-keywords "^3.1.0" - -schema-utils@^2.5.0, schema-utils@^2.6.5: - version "2.6.5" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.6.5.tgz#c758f0a7e624263073d396e29cd40aa101152d8a" - integrity sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ== - dependencies: - ajv "^6.12.0" - ajv-keywords "^3.4.1" - -schema-utils@^2.6.1, schema-utils@^2.6.4: - version "2.6.4" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.6.4.tgz#a27efbf6e4e78689d91872ee3ccfa57d7bdd0f53" - integrity sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ== - dependencies: - ajv "^6.10.2" - ajv-keywords "^3.4.1" - -schema-utils@^2.6.6, schema-utils@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" - integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== - dependencies: - "@types/json-schema" "^7.0.4" - ajv "^6.12.2" - ajv-keywords "^3.4.1" - -schematics-utilities@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/schematics-utilities/-/schematics-utilities-2.0.1.tgz#c763ee078b27d41540a455f3def8f3900edb5d9d" - integrity sha512-8tOOrWfBLayBefH/9Gdj+4oZ9aO+joRy4sZeQQ1l/rEaPt+bdZchEgllTz5rz0L/nX0Q/vYUmKXZiPEt0LOiWg== - dependencies: - "@angular-devkit/core" "^8.3.21" - "@angular-devkit/schematics" "^8.3.21" - "@schematics/angular" "^8.3.21" - "@schematics/update" "^0.803.21" - rxjs "^6.4.0" - typescript "^3.6.3" - optionalDependencies: - parse5 "^5.1.0" - -select-hose@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" - integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= - -selenium-webdriver@2.48.2: - version "2.48.2" - resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-2.48.2.tgz#b26a4631430d0a9f36284ee0cfe09676e8f348ca" - integrity sha1-smpGMUMNCp82KE7gz+CWdujzSMo= - dependencies: - adm-zip "0.4.4" - rimraf "^2.2.8" - tmp "0.0.24" - ws "^0.8.0" - xml2js "0.4.4" - -selfsigned@^1.10.7: - version "1.10.7" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.7.tgz#da5819fd049d5574f28e88a9bcc6dbc6e6f3906b" - integrity sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA== - dependencies: - node-forge "0.9.0" - -semver-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" - integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= - -semver-diff@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" - integrity sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY= - dependencies: - semver "^5.0.3" - -semver-diff@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" - integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== - dependencies: - semver "^6.3.0" - -semver-dsl@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/semver-dsl/-/semver-dsl-1.0.1.tgz#d3678de5555e8a61f629eed025366ae5f27340a0" - integrity sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA= - dependencies: - semver "^5.3.0" - -semver-intersect@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/semver-intersect/-/semver-intersect-1.4.0.tgz#bdd9c06bedcdd2fedb8cd352c3c43ee8c61321f3" - integrity sha512-d8fvGg5ycKAq0+I6nfWeCx6ffaWJCsBYU0H2Rq56+/zFePYfT8mXkB3tWBSjR5BerkHNZ5eTPIk1/LBYas35xQ== - dependencies: - semver "^5.0.0" - -semver-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-2.0.0.tgz#a93c2c5844539a770233379107b38c7b4ac9d338" - integrity sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw== - -"semver@2 || 3 || 4 || 5", semver@^5.0.0, semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -semver@2.x: - version "2.3.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-2.3.2.tgz#b9848f25d6cf36333073ec9ef8856d42f1233e52" - integrity sha1-uYSPJdbPNjMwc+ye+IVtQvEjPlI= - -semver@6.3.0, semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -semver@7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" - integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== - -semver@7.1.3, semver@^7.1.3: - version "7.1.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.3.tgz#e4345ce73071c53f336445cfc19efb1c311df2a6" - integrity sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA== - -semver@^4.3.3: - version "4.3.6" - resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" - integrity sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto= - -semver@^7.0.0, semver@^7.1.1: - version "7.3.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" - integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== - -semver@~5.0.1: - version "5.0.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a" - integrity sha1-d0Zt5YnNXTyV8TiqeLxWmjy10no= - -send@0.17.1: - version "0.17.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" - integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== - dependencies: - debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "~1.7.2" - mime "1.6.0" - ms "2.1.1" - on-finished "~2.3.0" - range-parser "~1.2.1" - statuses "~1.5.0" - -serialize-javascript@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" - integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== - -serialize-javascript@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.1.0.tgz#8bf3a9170712664ef2561b44b691eafe399214ea" - integrity sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg== - dependencies: - randombytes "^2.1.0" - -serve-index@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" - integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk= - dependencies: - accepts "~1.3.4" - batch "0.6.1" - debug "2.6.9" - escape-html "~1.0.3" - http-errors "~1.6.2" - mime-types "~2.1.17" - parseurl "~1.3.2" - -serve-static@1.14.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" - integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.17.1" - -set-blocking@^2.0.0, set-blocking@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -setimmediate@^1.0.4, setimmediate@~1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= - -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== - -setprototypeof@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" - integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -sha.js@^2.4.0, sha.js@^2.4.8: - version "2.4.11" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" - integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= - dependencies: - shebang-regex "^1.0.0" - -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= - -shelljs@^0.8.0, shelljs@^0.8.3: - version "0.8.3" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.3.tgz#a7f3319520ebf09ee81275b2368adb286659b097" - integrity sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A== - dependencies: - glob "^7.0.0" - interpret "^1.0.0" - rechoir "^0.6.2" - -side-channel@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.2.tgz#df5d1abadb4e4bf4af1cd8852bf132d2f7876947" - integrity sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA== - dependencies: - es-abstract "^1.17.0-next.1" - object-inspect "^1.7.0" - -sigmund@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" - integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA= - -signal-exit@^3.0.0, signal-exit@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= - -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= - dependencies: - is-arrayish "^0.3.1" - -slash@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" - integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -smart-buffer@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.1.0.tgz#91605c25d91652f4661ea69ccf45f1b331ca21ba" - integrity sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw== - -snakeize@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/snakeize/-/snakeize-0.1.0.tgz#10c088d8b58eb076b3229bb5a04e232ce126422d" - integrity sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0= - -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -sntp@1.x.x: - version "1.0.9" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" - integrity sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg= - dependencies: - hoek "2.x.x" - -socket.io-adapter@~1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz#ab3f0d6f66b8fc7fca3959ab5991f82221789be9" - integrity sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g== - -socket.io-client@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.1.1.tgz#dcb38103436ab4578ddb026638ae2f21b623671f" - integrity sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ== - dependencies: - backo2 "1.0.2" - base64-arraybuffer "0.1.5" - component-bind "1.0.0" - component-emitter "1.2.1" - debug "~3.1.0" - engine.io-client "~3.2.0" - has-binary2 "~1.0.2" - has-cors "1.1.0" - indexof "0.0.1" - object-component "0.0.3" - parseqs "0.0.5" - parseuri "0.0.5" - socket.io-parser "~3.2.0" - to-array "0.1.4" - -socket.io-parser@~3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.2.0.tgz#e7c6228b6aa1f814e6148aea325b51aa9499e077" - integrity sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA== - dependencies: - component-emitter "1.2.1" - debug "~3.1.0" - isarray "2.0.1" - -socket.io@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.1.1.tgz#a069c5feabee3e6b214a75b40ce0652e1cfb9980" - integrity sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA== - dependencies: - debug "~3.1.0" - engine.io "~3.2.0" - has-binary2 "~1.0.2" - socket.io-adapter "~1.1.0" - socket.io-client "2.1.1" - socket.io-parser "~3.2.0" - -sockjs-client@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5" - integrity sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g== - dependencies: - debug "^3.2.5" - eventsource "^1.0.7" - faye-websocket "~0.11.1" - inherits "^2.0.3" - json3 "^3.3.2" - url-parse "^1.4.3" - -sockjs@0.3.20: - version "0.3.20" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.20.tgz#b26a283ec562ef8b2687b44033a4eeceac75d855" - integrity sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA== - dependencies: - faye-websocket "^0.10.0" - uuid "^3.4.0" - websocket-driver "0.6.5" - -socks-proxy-agent@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz#3c8991f3145b2799e70e11bd5fbc8b1963116386" - integrity sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg== - dependencies: - agent-base "~4.2.1" - socks "~2.3.2" - -socks@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.3.3.tgz#01129f0a5d534d2b897712ed8aceab7ee65d78e3" - integrity sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA== - dependencies: - ip "1.1.5" - smart-buffer "^4.1.0" - -sort-keys@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" - integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0= - dependencies: - is-plain-obj "^1.0.0" - -source-list-map@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" - integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== - -source-map-loader@0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-0.2.4.tgz#c18b0dc6e23bf66f6792437557c569a11e072271" - integrity sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ== - dependencies: - async "^2.5.0" - loader-utils "^1.1.0" - -source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" - integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-resolve@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2" - integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - -source-map-support@^0.4.0, source-map-support@^0.4.15: - version "0.4.18" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" - integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== - dependencies: - source-map "^0.5.6" - -source-map-support@^0.5.5, source-map-support@~0.5.12: - version "0.5.16" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" - integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map-support@~0.2.8: - version "0.2.10" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.2.10.tgz#ea5a3900a1c1cb25096a0ae8cc5c2b4b10ded3dc" - integrity sha1-6lo5AKHByyUJagrozFwrSxDe09w= - dependencies: - source-map "0.1.32" - -source-map-support@~0.3.2: - version "0.3.3" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.3.3.tgz#34900977d5ba3f07c7757ee72e73bb1a9b53754f" - integrity sha1-NJAJd9W6PwfHdX7nLnO7GptTdU8= - dependencies: - source-map "0.1.32" - -source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= - -source-map@0.1.32: - version "0.1.32" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.32.tgz#c8b6c167797ba4740a8ea33252162ff08591b266" - integrity sha1-yLbBZ3l7pHQKjqMyUhYv8IWRsmY= - dependencies: - amdefine ">=0.0.4" - -source-map@0.7.3, source-map@^0.7.3: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - -source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -sourcemap-codec@^1.4.4, sourcemap-codec@^1.4.8: - version "1.4.8" - resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" - integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== - -spdx-correct@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" - integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q== - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" - integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== - -spdx-expression-parse@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" - integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.5" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" - integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== - -spdy-transport@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" - integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== - dependencies: - debug "^4.1.0" - detect-node "^2.0.4" - hpack.js "^2.1.6" - obuf "^1.1.2" - readable-stream "^3.0.6" - wbuf "^1.7.3" - -spdy@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" - integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== - dependencies: - debug "^4.1.0" - handle-thing "^2.0.0" - http-deceiver "^1.2.7" - select-hose "^2.0.0" - spdy-transport "^3.0.0" - -speed-measure-webpack-plugin@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.3.1.tgz#69840a5cdc08b4638697dac7db037f595d7f36a0" - integrity sha512-qVIkJvbtS9j/UeZumbdfz0vg+QfG/zxonAjzefZrqzkr7xOncLVXkeGbTpzd1gjCBM4PmVNkWlkeTVhgskAGSQ== - dependencies: - chalk "^2.0.1" - -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - -split2@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/split2/-/split2-2.2.0.tgz#186b2575bcf83e85b7d18465756238ee4ee42493" - integrity sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw== - dependencies: - through2 "^2.0.2" - -split@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" - integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== - dependencies: - through "2" - -sprintf-js@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" - integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - -sshpk@^1.7.0: - version "1.16.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" - integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - -ssri@^6.0.0, ssri@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" - integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA== - dependencies: - figgy-pudding "^3.5.1" - -ssri@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.0.tgz#79ca74e21f8ceaeddfcb4b90143c458b8d988808" - integrity sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA== - dependencies: - minipass "^3.1.1" - -stable@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" - integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== - -stack-trace@0.0.x: - version "0.0.10" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" - integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - -"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= - -stream-browserify@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" - integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== - dependencies: - inherits "~2.0.1" - readable-stream "^2.0.2" - -stream-each@^1.1.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" - integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw== - dependencies: - end-of-stream "^1.1.0" - stream-shift "^1.0.0" - -stream-events@^1.0.1, stream-events@^1.0.4, stream-events@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/stream-events/-/stream-events-1.0.5.tgz#bbc898ec4df33a4902d892333d47da9bf1c406d5" - integrity sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg== - dependencies: - stubs "^3.0.0" - -stream-http@^2.7.2: - version "2.8.3" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" - integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== - dependencies: - builtin-status-codes "^3.0.0" - inherits "^2.0.1" - readable-stream "^2.3.6" - to-arraybuffer "^1.0.0" - xtend "^4.0.0" - -stream-shift@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" - integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== - -streamroller@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-1.0.6.tgz#8167d8496ed9f19f05ee4b158d9611321b8cacd9" - integrity sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg== - dependencies: - async "^2.6.2" - date-format "^2.0.0" - debug "^3.2.6" - fs-extra "^7.0.1" - lodash "^4.17.14" - -streamsearch@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" - integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= - -strict-uri-encode@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" - integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= - -string-length@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-1.0.1.tgz#56970fb1c38558e9e70b728bf3de269ac45adfac" - integrity sha1-VpcPscOFWOnnC3KL894mmsRa36w= - dependencies: - strip-ansi "^3.0.0" - -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string-width@^3.0.0, string-width@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" - -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" - integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" - -string.prototype.trimleft@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" - integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag== - dependencies: - define-properties "^1.1.3" - function-bind "^1.1.1" - -string.prototype.trimright@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9" - integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g== - dependencies: - define-properties "^1.1.3" - function-bind "^1.1.1" - -string_decoder@^1.0.0, string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -stringstream@~0.0.4: - version "0.0.6" - resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72" - integrity sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA== - -strip-ansi@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.3.0.tgz#25f48ea22ca79187f3174a4db8759347bb126220" - integrity sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA= - dependencies: - ansi-regex "^0.2.1" - -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - -strip-ansi@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" - integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== - dependencies: - ansi-regex "^5.0.0" - -strip-bom@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" - integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4= - dependencies: - is-utf8 "^0.2.0" - -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= - -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= - -strip-indent@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" - integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI= - dependencies: - get-stdin "^4.0.1" - -strip-indent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" - integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g= - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - -stubs@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/stubs/-/stubs-3.0.0.tgz#e8d2ba1fa9c90570303c030b6900f7d5f89abe5b" - integrity sha1-6NK6H6nJBXAwPAMLaQD31fiavls= - -style-loader@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.1.3.tgz#9e826e69c683c4d9bf9db924f85e9abb30d5e200" - integrity sha512-rlkH7X/22yuwFYK357fMN/BxYOorfnfq0eD7+vqlemSK4wEcejFF1dg4zxP0euBW8NrYx2WZzZ8PPFevr7D+Kw== - dependencies: - loader-utils "^1.2.3" - schema-utils "^2.6.4" - -stylehacks@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" - integrity sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g== - dependencies: - browserslist "^4.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" - -stylus-loader@3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/stylus-loader/-/stylus-loader-3.0.2.tgz#27a706420b05a38e038e7cacb153578d450513c6" - integrity sha512-+VomPdZ6a0razP+zinir61yZgpw2NfljeSsdUF5kJuEzlo3khXhY19Fn6l8QQz1GRJGtMCo8nG5C04ePyV7SUA== - dependencies: - loader-utils "^1.0.2" - lodash.clonedeep "^4.5.0" - when "~3.6.x" - -stylus@0.54.7, stylus@^0.54.7: - version "0.54.7" - resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.54.7.tgz#c6ce4793965ee538bcebe50f31537bfc04d88cd2" - integrity sha512-Yw3WMTzVwevT6ZTrLCYNHAFmanMxdylelL3hkWNgPMeTCpMwpV3nXjpOHuBXtFv7aiO2xRuQS6OoAdgkNcSNug== - dependencies: - css-parse "~2.0.0" - debug "~3.1.0" - glob "^7.1.3" - mkdirp "~0.5.x" - safer-buffer "^2.1.2" - sax "~1.2.4" - semver "^6.0.0" - source-map "^0.7.3" - -superstatic@^6.0.1: - version "6.0.4" - resolved "https://registry.yarnpkg.com/superstatic/-/superstatic-6.0.4.tgz#5c38fe05e2e9513b68d5ba2798925e4839c151dd" - integrity sha512-Nfli9mSPa9fJloKuDeUOdqC1lcw4c4SnxiWPB8s7Yn1iYo7Ja3pj7qc8AXMqHVqn/Kf7QsxBjAeOJTpuJ0mcrQ== - dependencies: - as-array "^2.0.0" - async "^1.5.2" - basic-auth-connect "^1.0.0" - chalk "^1.1.3" - char-spinner "^1.0.1" - compare-semver "^1.0.0" - compression "^1.7.0" - connect "^3.6.2" - connect-query "^1.0.0" - destroy "^1.0.4" - fast-url-parser "^1.1.3" - fs-extra "^0.30.0" - glob "^7.1.2" - glob-slasher "^1.0.1" - home-dir "^1.0.0" - is-url "^1.2.2" - join-path "^1.1.1" - lodash "^4.17.4" - mime-types "^2.1.16" - minimatch "^3.0.4" - morgan "^1.8.2" - nash "^3.0.0" - on-finished "^2.2.0" - on-headers "^1.0.0" - path-to-regexp "^1.7.0" - router "^1.3.1" - rsvp "^3.6.2" - string-length "^1.0.0" - try-require "^1.0.0" - update-notifier "^2.5.0" - -supports-color@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a" - integrity sha1-2S3iaU6z9nMjlz1649i1W0wiGQo= - -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= - -supports-color@^5.0.0, supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.0.0, supports-color@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" - integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== - dependencies: - has-flag "^4.0.0" - -supports-hyperlinks@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-1.0.1.tgz#71daedf36cc1060ac5100c351bb3da48c29c0ef7" - integrity sha512-HHi5kVSefKaJkGYXbDuKbUGRVxqnWGn3J2e39CYcNJEfWciGq2zYtOhXLTlvrOZW1QU7VX67w7fMmWafHX9Pfw== - dependencies: - has-flag "^2.0.0" - supports-color "^5.0.0" - -svgo@^1.0.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" - integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== - dependencies: - chalk "^2.4.1" - coa "^2.0.2" - css-select "^2.0.0" - css-select-base-adapter "^0.1.1" - css-tree "1.0.0-alpha.37" - csso "^4.0.2" - js-yaml "^3.13.1" - mkdirp "~0.5.1" - object.values "^1.1.0" - sax "~1.2.4" - stable "^0.1.8" - unquote "~1.1.1" - util.promisify "~1.0.0" - -symbol-observable@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" - integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== - -systemjs-builder@^0.15.7: - version "0.15.36" - resolved "https://registry.yarnpkg.com/systemjs-builder/-/systemjs-builder-0.15.36.tgz#30b02372d4227cf37880f580fe67cb4edb7f1420" - integrity sha1-MLAjctQifPN4gPWA/mfLTtt/FCA= - dependencies: - babel-core "^6.9.0" - babel-plugin-transform-cjs-system-wrapper "^0.3.0" - babel-plugin-transform-es2015-modules-systemjs "^6.6.5" - babel-plugin-transform-global-system-wrapper "0.0.1" - babel-plugin-transform-system-register "0.0.1" - bluebird "^3.3.4" - data-uri-to-buffer "0.0.4" - es6-template-strings "^2.0.0" - glob "^7.0.3" - mkdirp "^0.5.1" - rollup "^0.36.3" - source-map "^0.5.3" - systemjs "^0.19.43" - traceur "0.0.105" - uglify-js "~2.7.5" - -systemjs@^0.19.16, systemjs@^0.19.43: - version "0.19.47" - resolved "https://registry.yarnpkg.com/systemjs/-/systemjs-0.19.47.tgz#c8c93937180f3f5481c769cd2720763fb4a31c6f" - integrity sha1-yMk5NxgPP1SBx2nNJyB2P7SjHG8= - dependencies: - when "^3.7.5" - -tapable@^1.0.0, tapable@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" - integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== - -tar-stream@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3" - integrity sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw== - dependencies: - bl "^3.0.0" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - -tar@^4.3.0, tar@^4.4.10, tar@^4.4.2, tar@^4.4.8: - version "4.4.13" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" - integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.8.6" - minizlib "^1.2.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.3" - -tar@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.1.tgz#7b3bd6c313cb6e0153770108f8d70ac298607efa" - integrity sha512-bKhKrrz2FJJj5s7wynxy/fyxpE0CmCjmOQ1KV4KkgXFWOgoIT/NbTMnB1n+LFNrNk0SSBVGGxcK5AGsyC+pW5Q== - dependencies: - chownr "^1.1.3" - fs-minipass "^2.0.0" - minipass "^3.0.0" - minizlib "^2.1.0" - mkdirp "^1.0.3" - yallist "^4.0.0" - -tar@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.2.tgz#5df17813468a6264ff14f766886c622b84ae2f39" - integrity sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg== - dependencies: - chownr "^2.0.0" - fs-minipass "^2.0.0" - minipass "^3.0.0" - minizlib "^2.1.0" - mkdirp "^1.0.3" - yallist "^4.0.0" - -tcp-port-used@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/tcp-port-used/-/tcp-port-used-1.0.1.tgz#46061078e2d38c73979a2c2c12b5a674e6689d70" - integrity sha512-rwi5xJeU6utXoEIiMvVBMc9eJ2/ofzB+7nLOdnZuFTmNCLqRiQh2sMG9MqCxHU/69VC/Fwp5dV9306Qd54ll1Q== - dependencies: - debug "4.1.0" - is2 "2.0.1" - -teeny-request@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-6.0.1.tgz#9b1f512cef152945827ba7e34f62523a4ce2c5b0" - integrity sha512-TAK0c9a00ELOqLrZ49cFxvPVogMUFaWY8dUsQc/0CuQPGF+BOxOQzXfE413BAk2kLomwNplvdtMpeaeGWmoc2g== - dependencies: - http-proxy-agent "^4.0.0" - https-proxy-agent "^4.0.0" - node-fetch "^2.2.0" - stream-events "^1.0.5" - uuid "^3.3.2" - -tempfile@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/tempfile/-/tempfile-1.1.1.tgz#5bcc4eaecc4ab2c707d8bc11d99ccc9a2cb287f2" - integrity sha1-W8xOrsxKsscH2LwR2ZzMmiyyh/I= - dependencies: - os-tmpdir "^1.0.0" - uuid "^2.0.1" - -term-size@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69" - integrity sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk= - dependencies: - execa "^0.7.0" - -term-size@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753" - integrity sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw== - -terser-webpack-plugin@3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-3.0.3.tgz#23bda2687b197f878a743373b9411d917adc2e45" - integrity sha512-bZFnotuIKq5Rqzrs+qIwFzGdKdffV9epG5vDSEbYzvKAhPeR5RbbrQysfPgbIIMhNAQtZD2hGwBfSKUXjXZZZw== - dependencies: - cacache "^15.0.4" - find-cache-dir "^3.3.1" - jest-worker "^26.0.0" - p-limit "^2.3.0" - schema-utils "^2.6.6" - serialize-javascript "^3.1.0" - source-map "^0.6.1" - terser "^4.6.13" - webpack-sources "^1.4.3" - -terser-webpack-plugin@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz#5ecaf2dbdc5fb99745fd06791f46fc9ddb1c9a7c" - integrity sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA== - dependencies: - cacache "^12.0.2" - find-cache-dir "^2.1.0" - is-wsl "^1.1.0" - schema-utils "^1.0.0" - serialize-javascript "^2.1.2" - source-map "^0.6.1" - terser "^4.1.2" - webpack-sources "^1.4.0" - worker-farm "^1.7.0" - -terser@4.6.10: - version "4.6.10" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.10.tgz#90f5bd069ff456ddbc9503b18e52f9c493d3b7c2" - integrity sha512-qbF/3UOo11Hggsbsqm2hPa6+L4w7bkr+09FNseEe8xrcVD3APGLFqE+Oz1ZKAxjYnFsj80rLOfgAtJ0LNJjtTA== - dependencies: - commander "^2.20.0" - source-map "~0.6.1" - source-map-support "~0.5.12" - -terser@^4.1.2, terser@^4.3.8: - version "4.6.3" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.3.tgz#e33aa42461ced5238d352d2df2a67f21921f8d87" - integrity sha512-Lw+ieAXmY69d09IIc/yqeBqXpEQIpDGZqT34ui1QWXIUpR2RjbqEkT8X7Lgex19hslSqcWM5iMN2kM11eMsESQ== - dependencies: - commander "^2.20.0" - source-map "~0.6.1" - source-map-support "~0.5.12" - -terser@^4.6.13: - version "4.8.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" - integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== - dependencies: - commander "^2.20.0" - source-map "~0.6.1" - source-map-support "~0.5.12" - -text-extensions@^1.0.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26" - integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ== - -text-hex@1.0.x: - version "1.0.0" - resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" - integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== - -through2@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.1.tgz#384e75314d49f32de12eebb8136b8eb6b5d59da9" - integrity sha1-OE51MU1J8y3hLuu4E2uOtrXVnak= - dependencies: - readable-stream "~2.0.0" - xtend "~4.0.0" - -through2@^2.0.0, through2@^2.0.2: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - -through2@^3.0.0, through2@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.1.tgz#39276e713c3302edf9e388dd9c812dd3b825bd5a" - integrity sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww== - dependencies: - readable-stream "2 || 3" - -through@2, "through@>=2.2.7 <3", through@X.X.X, through@^2.3.6: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= - -thunky@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" - integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== - -timed-out@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" - integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= - -timers-browserify@^2.0.4: - version "2.0.11" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" - integrity sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ== - dependencies: - setimmediate "^1.0.4" - -timers-ext@^0.1.5: - version "0.1.7" - resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6" - integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ== - dependencies: - es5-ext "~0.10.46" - next-tick "1" - -timsort@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" - integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= - -tmp@0.0.24: - version "0.0.24" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.24.tgz#d6a5e198d14a9835cc6f2d7c3d9e302428c8cf12" - integrity sha1-1qXhmNFKmDXMby18PZ4wJCjIzxI= - -tmp@0.0.33, tmp@0.0.x, tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - -to-array@0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" - integrity sha1-F+bBH3PdTz10zaek/zI46a2b+JA= - -to-arraybuffer@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" - integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= - -to-fast-properties@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" - integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc= - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= - -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= - dependencies: - kind-of "^3.0.2" - -to-readable-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" - integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - -toidentifier@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" - integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== - -tough-cookie@>=0.12.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" - integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg== - dependencies: - ip-regex "^2.1.0" - psl "^1.1.28" - punycode "^2.1.1" - -tough-cookie@~2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" - integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== - dependencies: - psl "^1.1.24" - punycode "^1.4.1" - -toxic@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toxic/-/toxic-1.0.1.tgz#8c2e2528da591100adc3883f2c0e56acfb1c7288" - integrity sha512-WI3rIGdcaKULYg7KVoB0zcjikqvcYYvcuT6D89bFPz2rVR0Rl0PK6x8/X62rtdLtBKIE985NzVf/auTtGegIIg== - dependencies: - lodash "^4.17.10" - -traceur@0.0.105: - version "0.0.105" - resolved "https://registry.yarnpkg.com/traceur/-/traceur-0.0.105.tgz#5cf9dee83d6b77861c3d6c44d53859aed7ab0479" - integrity sha1-XPne6D1rd4YcPWxE1ThZrterBHk= - dependencies: - commander "2.9.x" - glob "5.0.x" - rsvp "^3.0.13" - semver "^4.3.3" - source-map-support "~0.2.8" - -traceur@0.0.96: - version "0.0.96" - resolved "https://registry.yarnpkg.com/traceur/-/traceur-0.0.96.tgz#5f709b896e3b17fe0326503a67f34ab4baccabdd" - integrity sha1-X3CbiW47F/4DJlA6Z/NKtLrMq90= - dependencies: - commander "2.6" - glob "4.3" - rsvp "^3.0.13" - semver "2.x" - source-map-support "~0.2.8" - -"traverse@>=0.3.0 <0.4": - version "0.3.9" - resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" - integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= - -tree-kill@1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" - integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== - -trim-newlines@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" - integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= - -trim-newlines@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-2.0.0.tgz#b403d0b91be50c331dfc4b82eeceb22c3de16d20" - integrity sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA= - -trim-off-newlines@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3" - integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM= - -trim-right@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" - integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= - -triple-beam@^1.2.0, triple-beam@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" - integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== - -try-require@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/try-require/-/try-require-1.2.1.tgz#34489a2cac0c09c1cc10ed91ba011594d4333be2" - integrity sha1-NEiaLKwMCcHMEO2RugEVlNQzO+I= - -tsconfig-paths@^3.8.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" - integrity sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw== - dependencies: - "@types/json5" "^0.0.29" - json5 "^1.0.1" - minimist "^1.2.0" - strip-bom "^3.0.0" - -tsickle@^0.35.0: - version "0.35.0" - resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.35.0.tgz#59235df45937c0ec5d072c616c26d2d97fba54b9" - integrity sha512-irsZLX4293YUl9TuwNC5Fy020eLSc4bC3LfKnxnx1oq5wmZD9zSP8qvNNTiwRmf2/rxH+58JINcTARDjuvn+oQ== - dependencies: - minimist "^1.2.0" - mkdirp "^0.5.1" - source-map "^0.7.3" - -tslib@1.10.0, tslib@^1.10.0, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" - integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== - -tslib@1.11.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" - integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== - -"tslib@^1.10.0 || ^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.0.tgz#18d13fc2dce04051e20f074cc8387fd8089ce4f3" - integrity sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g== - -tslint@^5.17.0: - version "5.20.1" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.20.1.tgz#e401e8aeda0152bc44dd07e614034f3f80c67b7d" - integrity sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg== - dependencies: - "@babel/code-frame" "^7.0.0" - builtin-modules "^1.1.1" - chalk "^2.3.0" - commander "^2.12.1" - diff "^4.0.1" - glob "^7.1.1" - js-yaml "^3.13.1" - minimatch "^3.0.4" - mkdirp "^0.5.1" - resolve "^1.3.2" - semver "^5.3.0" - tslib "^1.8.0" - tsutils "^2.29.0" - -tsutils@^2.29.0: - version "2.29.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" - integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== - dependencies: - tslib "^1.8.1" - -tty-browserify@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" - integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= - dependencies: - safe-buffer "^5.0.1" - -tunnel-agent@~0.4.0: - version "0.4.3" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" - integrity sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us= - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= - -type-fest@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" - integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== - -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== - -type-is@~1.6.17, type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -type@^1.0.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" - integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== - -type@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/type/-/type-2.0.0.tgz#5f16ff6ef2eb44f260494dae271033b29c09a9c3" - integrity sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow== - -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= - -typedoc-default-themes@^0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/typedoc-default-themes/-/typedoc-default-themes-0.7.2.tgz#1e9896f920b58e6da0bba9d7e643738d02405a5a" - integrity sha512-fiFKlFO6VTqjcno8w6WpTsbCgXmfPHVjnLfYkmByZE7moaz+E2DSpAT+oHtDHv7E0BM5kAhPrHJELP2J2Y2T9A== - dependencies: - backbone "^1.4.0" - jquery "^3.4.1" - lunr "^2.3.8" - underscore "^1.9.1" - -typedoc@^0.16.4: - version "0.16.9" - resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.16.9.tgz#d6f46f4dea7d3362029927a92981efdf896f435b" - integrity sha512-UvOGoy76yqwCXwxPgatwgXWfsQ3FczyZ6ZNLjhCPK+TsDir6LiU3YB6N9XZmPv36E+7LA860mnc8a0v6YADKFw== - dependencies: - "@types/minimatch" "3.0.3" - fs-extra "^8.1.0" - handlebars "^4.7.2" - highlight.js "^9.17.1" - lodash "^4.17.15" - marked "^0.8.0" - minimatch "^3.0.0" - progress "^2.0.3" - shelljs "^0.8.3" - typedoc-default-themes "^0.7.2" - typescript "3.7.x" - -typescript@3.6.5, typescript@~3.6.4: - version "3.6.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.5.tgz#dae20114a7b4ff4bd642db9c8c699f2953e8bbdb" - integrity sha512-BEjlc0Z06ORZKbtcxGrIvvwYs5hAnuo6TKdNFL55frVDlB+na3z5bsLhFaIxmT+dPWgBIjMo6aNnTOgHHmHgiQ== - -typescript@3.7.x, typescript@^3.6.3: - version "3.7.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" - integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== - -uglify-js@^3.1.4: - version "3.7.6" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.7.6.tgz#0783daa867d4bc962a37cc92f67f6e3238c47485" - integrity sha512-yYqjArOYSxvqeeiYH2VGjZOqq6SVmhxzaPjJC1W2F9e+bqvFL9QXQ2osQuKUFjM2hGjKG2YclQnRKWQSt/nOTQ== - dependencies: - commander "~2.20.3" - source-map "~0.6.1" - -uglify-js@~2.7.5: - version "2.7.5" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8" - integrity sha1-RhLAx7qu4rp8SH3kkErhIgefLKg= - dependencies: - async "~0.2.6" - source-map "~0.5.1" - uglify-to-browserify "~1.0.0" - yargs "~3.10.0" - -uglify-to-browserify@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" - integrity sha1-bgkk1r2mta/jSeOabWMoUKD4grc= - -ultron@1.0.x: - version "1.0.2" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" - integrity sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po= - -ultron@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" - integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og== - -underscore@>=1.8.3, underscore@^1.9.1: - version "1.9.2" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.2.tgz#0c8d6f536d6f378a5af264a72f7bec50feb7cf2f" - integrity sha512-D39qtimx0c1fI3ya1Lnhk3E9nONswSKhnffBI0gME9C99fYOkNi04xs8K6pePLhvl1frbDemkaBQ5ikWllR2HQ== - -unicode-canonical-property-names-ecmascript@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" - integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ== - -unicode-match-property-ecmascript@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" - integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg== - dependencies: - unicode-canonical-property-names-ecmascript "^1.0.4" - unicode-property-aliases-ecmascript "^1.0.4" - -unicode-match-property-value-ecmascript@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz#5b4b426e08d13a80365e0d657ac7a6c1ec46a277" - integrity sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g== - -unicode-match-property-value-ecmascript@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" - integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ== - -unicode-property-aliases-ecmascript@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz#a9cc6cc7ce63a0a3023fc99e341b94431d405a57" - integrity sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw== - -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - -uniq@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" - integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= - -uniqs@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" - integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= - -unique-filename@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" - integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== - dependencies: - unique-slug "^2.0.0" - -unique-slug@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" - integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== - dependencies: - imurmurhash "^0.1.4" - -unique-string@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" - integrity sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo= - dependencies: - crypto-random-string "^1.0.0" - -unique-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" - integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== - dependencies: - crypto-random-string "^2.0.0" - -universal-analytics@0.4.20, universal-analytics@^0.4.16: - version "0.4.20" - resolved "https://registry.yarnpkg.com/universal-analytics/-/universal-analytics-0.4.20.tgz#d6b64e5312bf74f7c368e3024a922135dbf24b03" - integrity sha512-gE91dtMvNkjO+kWsPstHRtSwHXz0l2axqptGYp5ceg4MsuurloM0PU3pdOfpb5zBXUvyjT4PwhWK2m39uczZuw== - dependencies: - debug "^3.0.0" - request "^2.88.0" - uuid "^3.0.0" - -universalify@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== - -universalify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" - integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= - -unquote@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" - integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= - -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -unzip-response@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" - integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c= - -unzipper@^0.10.10: - version "0.10.10" - resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.10.10.tgz#d82d41fbdfa1f0731123eb11c2cfc028b45d3d42" - integrity sha512-wEgtqtrnJ/9zIBsQb8UIxOhAH1eTHfi7D/xvmrUoMEePeI6u24nq1wigazbIFtHt6ANYXdEVTvc8XYNlTurs7A== - dependencies: - big-integer "^1.6.17" - binary "~0.3.0" - bluebird "~3.4.1" - buffer-indexof-polyfill "~1.0.0" - duplexer2 "~0.1.4" - fstream "^1.0.12" - graceful-fs "^4.2.2" - listenercount "~1.0.1" - readable-stream "~2.3.6" - setimmediate "~1.0.4" - -upath@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" - integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== - -update-notifier@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6" - integrity sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw== - dependencies: - boxen "^1.2.1" - chalk "^2.0.1" - configstore "^3.0.0" - import-lazy "^2.1.0" - is-ci "^1.0.10" - is-installed-globally "^0.1.0" - is-npm "^1.0.0" - latest-version "^3.0.0" - semver-diff "^2.0.0" - xdg-basedir "^3.0.0" - -update-notifier@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.0.0.tgz#f344a6f8b03e00e31b323d632a0e632e9f0e0654" - integrity sha512-p9zf71hWt5GVXM4iEBujpUgx8mK9AWiCCapEJm/O1z5ntCim83Z1ATqzZFBHFYqx03laMqv8LiDgs/7ikXjf/g== - dependencies: - boxen "^4.2.0" - chalk "^3.0.0" - configstore "^5.0.0" - has-yarn "^2.1.0" - import-lazy "^2.1.0" - is-ci "^2.0.0" - is-installed-globally "^0.3.1" - is-npm "^4.0.0" - is-yarn-global "^0.3.0" - latest-version "^5.0.0" - semver-diff "^3.1.1" - xdg-basedir "^4.0.0" - -uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== - dependencies: - punycode "^2.1.0" - -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= - -url-join@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/url-join/-/url-join-0.0.1.tgz#1db48ad422d3402469a87f7d97bdebfe4fb1e3c8" - integrity sha1-HbSK1CLTQCRpqH99l73r/k+x48g= - -url-parse-lax@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" - integrity sha1-evjzA2Rem9eaJy56FKxovAYJ2nM= - dependencies: - prepend-http "^1.0.1" - -url-parse-lax@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" - integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= - dependencies: - prepend-http "^2.0.0" - -url-parse@^1.4.3: - version "1.4.7" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" - integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - -url@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= - dependencies: - punycode "1.3.2" - querystring "0.2.0" - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - -useragent@2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.3.0.tgz#217f943ad540cb2128658ab23fc960f6a88c9972" - integrity sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw== - dependencies: - lru-cache "4.1.x" - tmp "0.0.x" - -utf-8-validate@1.2.x: - version "1.2.2" - resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-1.2.2.tgz#8bb871a4741e085c70487ca7acdbd7d6d36029eb" - integrity sha1-i7hxpHQeCFxwSHynrNvX1tNgKes= - dependencies: - bindings "~1.2.1" - nan "~2.4.0" - -utf-8-validate@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.2.tgz#63cfbccd85dc1f2b66cf7a1d0eebc08ed056bfb3" - integrity sha512-SwV++i2gTD5qh2XqaPzBnNX88N6HdyhQrNNRykvcS0QKvItV9u3vPEJr+X5Hhfb1JC0r0e1alL0iB09rY8+nmw== - dependencies: - node-gyp-build "~3.7.0" - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -util-promisify@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/util-promisify/-/util-promisify-2.1.0.tgz#3c2236476c4d32c5ff3c47002add7c13b9a82a53" - integrity sha1-PCI2R2xNMsX/PEcAKt18E7moKlM= - dependencies: - object.getownpropertydescriptors "^2.0.3" - -util.promisify@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" - integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.2" - has-symbols "^1.0.1" - object.getownpropertydescriptors "^2.1.0" - -util@0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" - integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= - dependencies: - inherits "2.0.1" - -util@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" - integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== - dependencies: - inherits "2.0.3" - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= - -uuid@7.0.2: - version "7.0.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.2.tgz#7ff5c203467e91f5e0d85cfcbaaf7d2ebbca9be6" - integrity sha512-vy9V/+pKG+5ZTYKf+VcphF5Oc6EFiu3W8Nv3P3zIh0EqVI80ZxOzuPfe9EHjkFNvf8+xuTHVeei4Drydlx4zjw== - -uuid@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" - integrity sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho= - -uuid@^3.0.0, uuid@^3.3.2, uuid@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - -valid-url@^1: - version "1.0.9" - resolved "https://registry.yarnpkg.com/valid-url/-/valid-url-1.0.9.tgz#1c14479b40f1397a75782f115e4086447433a200" - integrity sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA= - -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" - integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - -validate-npm-package-name@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e" - integrity sha1-X6kS2B630MdK/BQN5zF/DKffQ34= - dependencies: - builtins "^1.0.3" - -vary@^1, vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= - -vendors@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" - integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - -vm-browserify@^1.0.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" - integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== - -void-elements@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" - integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w= - -walkdir@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/walkdir/-/walkdir-0.4.1.tgz#dc119f83f4421df52e3061e514228a2db20afa39" - integrity sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ== - -watchpack@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" - integrity sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA== - dependencies: - chokidar "^2.0.2" - graceful-fs "^4.1.2" - neo-async "^2.5.0" - -wbuf@^1.1.0, wbuf@^1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" - integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== - dependencies: - minimalistic-assert "^1.0.0" - -wcwidth@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" - integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= - dependencies: - defaults "^1.0.3" - -webpack-dev-middleware@3.7.2, webpack-dev-middleware@^3.7.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3" - integrity sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw== - dependencies: - memory-fs "^0.4.1" - mime "^2.4.4" - mkdirp "^0.5.1" - range-parser "^1.2.1" - webpack-log "^2.0.0" - -webpack-dev-server@3.11.0: - version "3.11.0" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz#8f154a3bce1bcfd1cc618ef4e703278855e7ff8c" - integrity sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg== - dependencies: - ansi-html "0.0.7" - bonjour "^3.5.0" - chokidar "^2.1.8" - compression "^1.7.4" - connect-history-api-fallback "^1.6.0" - debug "^4.1.1" - del "^4.1.1" - express "^4.17.1" - html-entities "^1.3.1" - http-proxy-middleware "0.19.1" - import-local "^2.0.0" - internal-ip "^4.3.0" - ip "^1.1.5" - is-absolute-url "^3.0.3" - killable "^1.0.1" - loglevel "^1.6.8" - opn "^5.5.0" - p-retry "^3.0.1" - portfinder "^1.0.26" - schema-utils "^1.0.0" - selfsigned "^1.10.7" - semver "^6.3.0" - serve-index "^1.9.1" - sockjs "0.3.20" - sockjs-client "1.4.0" - spdy "^4.0.2" - strip-ansi "^3.0.1" - supports-color "^6.1.0" - url "^0.11.0" - webpack-dev-middleware "^3.7.2" - webpack-log "^2.0.0" - ws "^6.2.1" - yargs "^13.3.2" - -webpack-log@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-2.0.0.tgz#5b7928e0637593f119d32f6227c1e0ac31e1b47f" - integrity sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg== - dependencies: - ansi-colors "^3.0.0" - uuid "^3.3.2" - -webpack-merge@4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d" - integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g== - dependencies: - lodash "^4.17.15" - -webpack-sources@1.4.3, webpack-sources@^1.1.0, webpack-sources@^1.2.0, webpack-sources@^1.3.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" - integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== - dependencies: - source-list-map "^2.0.0" - source-map "~0.6.1" - -webpack-subresource-integrity@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/webpack-subresource-integrity/-/webpack-subresource-integrity-1.4.0.tgz#44963a64c9a214ad729158e7f46d52c2525cc88a" - integrity sha512-GB1kB/LwAWC3CxwcedGhMkxGpNZxSheCe1q+KJP1bakuieAdX/rGHEcf5zsEzhKXpqsGqokgsDoD9dIkr61VDQ== - dependencies: - webpack-sources "^1.3.0" - -webpack@4.42.0: - version "4.42.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.42.0.tgz#b901635dd6179391d90740a63c93f76f39883eb8" - integrity sha512-EzJRHvwQyBiYrYqhyjW9AqM90dE4+s1/XtCfn7uWg6cS72zH+2VPFAlsnW0+W0cDi0XRjNKUMoJtpSi50+Ph6w== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-module-context" "1.8.5" - "@webassemblyjs/wasm-edit" "1.8.5" - "@webassemblyjs/wasm-parser" "1.8.5" - acorn "^6.2.1" - ajv "^6.10.2" - ajv-keywords "^3.4.1" - chrome-trace-event "^1.0.2" - enhanced-resolve "^4.1.0" - eslint-scope "^4.0.3" - json-parse-better-errors "^1.0.2" - loader-runner "^2.4.0" - loader-utils "^1.2.3" - memory-fs "^0.4.1" - micromatch "^3.1.10" - mkdirp "^0.5.1" - neo-async "^2.6.1" - node-libs-browser "^2.2.1" - schema-utils "^1.0.0" - tapable "^1.1.3" - terser-webpack-plugin "^1.4.3" - watchpack "^1.6.0" - webpack-sources "^1.4.1" - -websocket-driver@0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36" - integrity sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY= - dependencies: - websocket-extensions ">=0.1.1" - -websocket-driver@>=0.5.1: - version "0.7.3" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.3.tgz#a2d4e0d4f4f116f1e6297eba58b05d430100e9f9" - integrity sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg== - dependencies: - http-parser-js ">=0.4.0 <0.4.11" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" - integrity sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg== - -whatwg-fetch@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" - integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng== - -whatwg-fetch@>=0.10.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" - integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== - -when@^3.7.5: - version "3.7.8" - resolved "https://registry.yarnpkg.com/when/-/when-3.7.8.tgz#c7130b6a7ea04693e842cdc9e7a1f2aa39a39f82" - integrity sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I= - -when@~3.6.x: - version "3.6.4" - resolved "https://registry.yarnpkg.com/when/-/when-3.6.4.tgz#473b517ec159e2b85005497a13983f095412e34e" - integrity sha1-RztRfsFZ4rhQBUl6E5g/CVQS404= - -which-boxed-primitive@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz#cbe8f838ebe91ba2471bb69e9edbda67ab5a5ec1" - integrity sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ== - dependencies: - is-bigint "^1.0.0" - is-boolean-object "^1.0.0" - is-number-object "^1.0.3" - is-string "^1.0.4" - is-symbol "^1.0.2" - -which-collection@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" - integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== - dependencies: - is-map "^2.0.1" - is-set "^2.0.1" - is-weakmap "^2.0.1" - is-weakset "^2.0.1" - -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= - -which-pm-runs@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" - integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= - -which@^1.2.1, which@^1.2.9, which@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - -widest-line@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc" - integrity sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA== - dependencies: - string-width "^2.1.1" - -widest-line@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" - integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== - dependencies: - string-width "^4.0.0" - -window-size@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" - integrity sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0= - -window-size@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876" - integrity sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY= - -winston-transport@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.3.0.tgz#df68c0c202482c448d9b47313c07304c2d7c2c66" - integrity sha512-B2wPuwUi3vhzn/51Uukcao4dIduEiPOcOt9HJ3QeaXgkJ5Z7UwpBzxS4ZGNHtrxrUvTwemsQiSys0ihOf8Mp1A== - dependencies: - readable-stream "^2.3.6" - triple-beam "^1.2.0" - -winston@^3.0.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/winston/-/winston-3.2.1.tgz#63061377976c73584028be2490a1846055f77f07" - integrity sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw== - dependencies: - async "^2.6.1" - diagnostics "^1.1.1" - is-stream "^1.1.0" - logform "^2.1.1" - one-time "0.0.4" - readable-stream "^3.1.1" - stack-trace "0.0.x" - triple-beam "^1.3.0" - winston-transport "^4.3.0" - -wordwrap@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" - integrity sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8= - -wordwrap@~0.0.2: - version "0.0.3" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" - integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= - -worker-farm@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" - integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== - dependencies: - errno "~0.1.7" - -worker-plugin@4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/worker-plugin/-/worker-plugin-4.0.3.tgz#7c42e600d5931ad154d3d5f187a32446df64db0f" - integrity sha512-7hFDYWiKcE3yHZvemsoM9lZis/PzurHAEX1ej8PLCu818Rt6QqUAiDdxHPCKZctzmhqzPpcFSgvMCiPbtooqAg== - dependencies: - loader-utils "^1.1.0" - -wrap-ansi@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" - integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== - dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" - -wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -write-file-atomic@^2.0.0: - version "2.4.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" - integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ== - dependencies: - graceful-fs "^4.1.11" - imurmurhash "^0.1.4" - signal-exit "^3.0.2" - -write-file-atomic@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.1.tgz#558328352e673b5bb192cf86500d60b230667d4b" - integrity sha512-JPStrIyyVJ6oCSz/691fAjFtefZ6q+fP6tm+OS4Qw6o+TGQxNp1ziY2PgS+X/m0V8OWhZiO/m4xSj+Pr4RrZvw== - dependencies: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" - -ws@^0.8.0: - version "0.8.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-0.8.1.tgz#6b65273b99193c5f067a4cf5809598f777e3b759" - integrity sha1-a2UnO5kZPF8Gekz1gJWY93fjt1k= - dependencies: - options ">=0.0.5" - ultron "1.0.x" - optionalDependencies: - bufferutil "1.2.x" - utf-8-validate "1.2.x" - -ws@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== - dependencies: - async-limiter "~1.0.0" - -ws@^7.2.1: - version "7.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.1.tgz#03ed52423cd744084b2cf42ed197c8b65a936b8e" - integrity sha512-sucePNSafamSKoOqoNfBd8V0StlkzJKL2ZAhGQinCfNQ+oacw+Pk7lcdAElecBF2VkLNZRiIb5Oi1Q5lVUVt2A== - -ws@^7.2.3: - version "7.2.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.3.tgz#a5411e1fb04d5ed0efee76d26d5c46d830c39b46" - integrity sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ== - -ws@~3.3.1: - version "3.3.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" - integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA== - dependencies: - async-limiter "~1.0.0" - safe-buffer "~5.1.0" - ultron "~1.1.0" - -xdg-basedir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" - integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ= - -xdg-basedir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" - integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== - -xhr2@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.1.4.tgz#7f87658847716db5026323812f818cadab387a5f" - integrity sha1-f4dliEdxbbUCYyOBL4GMras4el8= - -xhr2@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.2.0.tgz#eddeff782f3b7551061b8d75645085269396e521" - integrity sha512-BDtiD0i2iKPK/S8OAZfpk6tyzEDnKKSjxWHcMBVmh+LuqJ8A32qXTyOx+TVOg2dKvq6zGBq2sgKPkEeRs1qTRA== - -xml2js@0.4.4: - version "0.4.4" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.4.tgz#3111010003008ae19240eba17497b57c729c555d" - integrity sha1-MREBAAMAiuGSQOuhdJe1fHKcVV0= - dependencies: - sax "0.6.x" - xmlbuilder ">=1.0.0" - -xmlbuilder@>=1.0.0: - version "13.0.2" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-13.0.2.tgz#02ae33614b6a047d1c32b5389c1fdacb2bce47a7" - integrity sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ== - -xmlbuilder@^9.0.7: - version "9.0.7" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" - integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= - -xmldom@0.1.x: - version "0.1.31" - resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff" - integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ== - -xmlhttprequest-ssl@~1.5.4: - version "1.5.5" - resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" - integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4= - -xmlhttprequest@1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" - integrity sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw= - -xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -xxhashjs@^0.2.1: - version "0.2.2" - resolved "https://registry.yarnpkg.com/xxhashjs/-/xxhashjs-0.2.2.tgz#8a6251567621a1c46a5ae204da0249c7f8caa9d8" - integrity sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw== - dependencies: - cuint "^0.2.2" - -y18n@^3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" - integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= - -y18n@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" - integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== - -yallist@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" - integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= - -yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yaml@^1.7.2: - version "1.10.0" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" - integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== - -yargs-parser@^13.1.2: - version "13.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" - integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-parser@^16.1.0: - version "16.1.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-16.1.0.tgz#73747d53ae187e7b8dbe333f95714c76ea00ecf1" - integrity sha512-H/V41UNZQPkUMIT5h5hiwg4QKIY1RPvoBV4XcjUbRM8Bk2oKqqyZ0DIEbTFZB0XjbtSPG8SAa/0DxCQmiRgzKg== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-parser@^18.1.0: - version "18.1.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" - integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs@15.3.0: - version "15.3.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.0.tgz#403af6edc75b3ae04bf66c94202228ba119f0976" - integrity sha512-g/QCnmjgOl1YJjGsnUg2SatC7NUYEiLXJqxNOQU9qSpjzGtGXda9b+OKccr1kLTy8BN9yqEyqfq5lxlwdc13TA== - dependencies: - cliui "^6.0.0" - decamelize "^1.2.0" - find-up "^4.1.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^4.2.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^18.1.0" - -yargs@^13.3.2: - version "13.3.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" - integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== - dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.2" - -yargs@^15.0.2: - version "15.1.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.1.0.tgz#e111381f5830e863a89550bd4b136bb6a5f37219" - integrity sha512-T39FNN1b6hCW4SOIk1XyTOWxtXdcen0t+XYrysQmChzSipvhBO8Bj0nK1ozAasdk24dNWuMZvr4k24nz+8HHLg== - dependencies: - cliui "^6.0.0" - decamelize "^1.2.0" - find-up "^4.1.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^4.2.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^16.1.0" - -yargs@^3.10.0: - version "3.32.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995" - integrity sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU= - dependencies: - camelcase "^2.0.1" - cliui "^3.0.3" - decamelize "^1.1.1" - os-locale "^1.4.0" - string-width "^1.0.1" - window-size "^0.1.4" - y18n "^3.2.0" - -yargs@~3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" - integrity sha1-9+572FfdfB0tOMDnTvvWgdFDH9E= - dependencies: - camelcase "^1.0.2" - cliui "^2.1.0" - decamelize "^1.0.0" - window-size "0.1.0" - -yeast@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" - integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk= - -zip-stream@^2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-2.1.3.tgz#26cc4bdb93641a8590dd07112e1f77af1758865b" - integrity sha512-EkXc2JGcKhO5N5aZ7TmuNo45budRaFGHOmz24wtJR7znbNqDPmdZtUauKX6et8KAVseAMBOyWJqEpXcHTBsh7Q== - dependencies: - archiver-utils "^2.1.0" - compress-commons "^2.1.1" - readable-stream "^3.4.0" - -zone.js@~0.10.2: - version "0.10.2" - resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.10.2.tgz#67ca084b3116fc33fc40435e0d5ea40a207e392e" - integrity sha512-UAYfiuvxLN4oyuqhJwd21Uxb4CNawrq6fPS/05Su5L4G+1TN+HVDJMUHNMobVQDFJRir2cLAODXwluaOKB7HFg==